Skip to content

fix(core): send execute progress at most every 100 ms - #60120

Merged
rekram1-node merged 2 commits into
devfrom
execute-progress-buffer
Oct 29, 2026
Merged

rekram1-node merged 2 commits into
devfrom
execute-progress-buffer

Conversation

@rekram1-node

Copy link
Copy Markdown
Contributor

Problem

The execute tool sends a progress update when each nested call starts and when it finishes. Every update carries all rows so far, including each call's input, and replaces the one before it. So a program with N nested calls sends 2N updates that keep getting bigger.

From my local sessions: median 1 nested call per execute, p99 16, max 922. The average row is 391 bytes. The 922-call run sent roughly 372 MB of progress (an estimate from the saved rows, not a measurement). Other tools only send progress once or twice per call, so only execute needs this.

Change

execute now sends at most one progress update per 100 ms, always with the latest rows. The rows themselves still update right away; only sending is held back.

program: 3 quick calls a, b, c, then a slow call s that takes 250 ms

before (one update per start and per finish)
t=0      [a…] [a✓] [a✓ b…] [a✓ b✓] [a✓ b✓ c…] [a✓ b✓ c✓] [a✓ b✓ c✓ s…]
t=250    [a✓ b✓ c✓ s✓]

after
t=0      [a…]                 first update goes out at once
t=100    [a✓ b✓ c✓ s…]        one trailing update with the latest rows
t=250    [a✓ b✓ c✓ s✓]        goes out at once, 100 ms have passed
  • If the call succeeds while an update is still waiting, I drop it. The result's metadata already has the final rows, and nothing can be sent after the call settles.
  • If the call fails or is interrupted, I send the waiting rows first, so the failure snapshot (which uses the last progress) stays current. In step.ts, interrupted tool fibers are awaited before failUnsettledTools, so this lands in time.
  • The wait uses Effect's Clock, so tests drive it with TestClock.

I left out a Visual section: nothing changes on screen besides rows refreshing at most every 100 ms.

Two existing tests asserted every intermediate update, and their calls finish within 100 ms, so the trailing "completed" update is now dropped. I updated them: "executions share the prepared runtime..." in tool-execute.test.ts (now on TestClock so it can't depend on timing) and "executes and reports progress for codemode tools..." in tool-registry.test.ts.

Out of scope

Progress that a nested tool sends itself through context.progress still goes straight through, unthrottled.

Verification

From packages/core:

  • bun run typecheck: no type errors
  • bun run typecheck:tsc: passes
  • bun run script/test.ts test/tool-execute.test.ts test/codemode test/session-runner.test.ts test/mcp.test.ts test/tool-registry.test.ts: 389 pass, 0 fail
  • bun run script/test.ts test/tool-execute.test.ts --rerun-each 30 --randomize: 270 pass, 0 fail
  • With src/codemode/tool.ts from dev, both new tests and the updated one in tool-execute.test.ts fail. With only the fail/interrupt flush removed, the interrupt test fails.
  • Full core suite: 7 failures, all shell tests (tool-shell.test.ts, shell-scan). They fail the same way locally with dev's source, so they're unrelated.

From the repo root: bun run check passes.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks sound. Progress updates are held to at most one per 100 ms with a trailing update. On failure or interruption the waiting rows are sent while holding the lock, and on success a waiting update is dropped when the new local scope closes. That is safe because the tool's execute type never asked for an outer Scope.

To check it, I ran the new and updated tool-execute tests against dev's source: they fail there and pass with this change. The nearby core suites pass (315 tests), 20 reruns of tool-execute.test.ts passed without a failure, and typecheck:tsc is clean. The tests use TestClock, so they don't depend on real timing.

Public API: unchanged (internal throttling of execute progress only).

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commit only adds an assertion that the result metadata still has the completed echo row now that the trailing progress update is dropped. tool-registry.test.ts passes (42 tests). The source is unchanged since my last review, so nothing new to raise.

Public API: unchanged.

@rekram1-node
rekram1-node merged commit b6f82bf into dev Oct 29, 2026
13 checks passed
@rekram1-node
rekram1-node deleted the execute-progress-buffer branch October 29, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant