feat(code): show "Took <duration>" after execute finishes (#4301)

`execute` tool calls now display how long the command took after it
finishes.

---

For `execute` shell tool calls, the status row now reports how long the
command ran (e.g. `Took 5s`) once the "Running..." spinner stops,
reusing the existing `format_duration` helper and the elapsed timer
started in `set_running`. Other tools still hide their status on success
since their output speaks for itself.

Made by [Open
SWE](https://openswe.vercel.app/agents/77f4f519-e99b-1012-d0fc-090de11baf58)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
Mason Daugherty
2026-07-02 00:01:12 -04:00
committed by GitHub
parent e4709c2d1e
commit a5240ebe36
2 changed files with 90 additions and 1 deletions
+15 -1
View File
@@ -1255,14 +1255,28 @@ class ToolCallMessage(Vertical):
def set_success(self, result: str = "") -> None:
"""Mark the tool call as successful.
For `execute` calls that actually ran (a start time was recorded via
`set_running`), the elapsed run time is shown in place of the usual
success marker; every other tool routes through `_show_success_status`.
Args:
result: Tool output/result to display
"""
elapsed = time() - self._start_time if self._start_time is not None else None
self._stop_animation()
self._status = "success"
# Strip redundant success trailer — the UI already conveys success
self._output = _strip_success_exit_line(result)
self._show_success_status()
if self._tool_name == "execute" and elapsed is not None and self._status_widget:
self._status_widget.remove_class("pending")
# `execute` calls can run for a while, so keep the row and report
# how long the command took once the spinner stops.
self._status_widget.update(
Content.styled(f"Took {format_duration(elapsed)}", "dim")
)
self._status_widget.display = True
else:
self._show_success_status()
self._update_output_display()
def _show_success_status(self) -> None:
@@ -675,6 +675,81 @@ class TestToolCallMessageMarkupSafety:
assert msg.has_expandable_args is True
class TestToolCallMessageDuration:
"""Tests for the post-run duration shown on `execute` tool calls."""
async def test_execute_shows_took_after_success(self) -> None:
"""`execute` keeps its status row and reports how long it ran."""
app = _tool_msg_app("execute", {"command": "sleep 1"})
async with app.run_test() as pilot:
await pilot.pause()
app.msg.set_running()
app.msg._start_time -= 5 # ty: ignore
app.msg.set_success("done")
await pilot.pause()
status = app.msg._status_widget
assert status is not None
assert status.display is True
content = status._Static__content # ty: ignore
assert isinstance(content, Content)
assert content.plain == "Took 5s"
async def test_execute_shows_fractional_seconds(self) -> None:
"""Sub-minute `execute` runs report tenths — `elapsed` is a float.
The running spinner truncates to whole seconds, but `set_success`
passes the raw float to `format_duration`, so a regression that
truncated `elapsed` to `int` would be caught here.
"""
app = _tool_msg_app("execute", {"command": "true"})
async with app.run_test() as pilot:
await pilot.pause()
app.msg.set_running()
app.msg._start_time -= 0.3 # ty: ignore
app.msg.set_success("done")
await pilot.pause()
status = app.msg._status_widget
assert status is not None
content = status._Static__content # ty: ignore
assert isinstance(content, Content)
assert content.plain == "Took 0.3s"
async def test_execute_without_run_falls_back_to_success_status(self) -> None:
"""`execute` success with no recorded start time hides the row.
Without a prior `set_running`, `_start_time` is `None`, so the
`elapsed is not None` guard must route to `_show_success_status`
(which hides the row here because output is present) rather than
computing a duration from `None` and crashing.
"""
app = _tool_msg_app("execute", {"command": "true"})
async with app.run_test() as pilot:
await pilot.pause()
app.msg.set_success("done")
await pilot.pause()
status = app.msg._status_widget
assert status is not None
assert status.display is False
async def test_non_execute_hides_status_on_success(self) -> None:
"""Non-`execute` tools hide the status row and never show a duration."""
app = _tool_msg_app("read_file", {"file_path": "a.py"})
async with app.run_test() as pilot:
await pilot.pause()
app.msg.set_running()
app.msg.set_success("contents")
await pilot.pause()
status = app.msg._status_widget
assert status is not None
assert status.display is False
content = status._Static__content # ty: ignore
assert "Took" not in getattr(content, "plain", str(content))
class TestToolCallMessageTodos:
"""Tests for `write_todos` output formatting."""