Repository navigation
fix(stream): pair and close every agent tool call - #183
Merged
surlymochan merged 1 commit intoNov 12, 2026
Merged
Conversation
The claude adapter dropped every `tool_result` line as an echo, so a tool call started and never ended. Neither adapter carried the CLI's own tool-call id, so nothing downstream could pair the two even when both were present, and a run killed mid-call left a call that stayed "open" forever in execution.ndjson. Emit `tool_call_id` on tool_start/tool_end (claude `tool_use.id` and `tool_use_id`, codex `item.id`), map claude `tool_result` blocks to a closing event with status `error` when `is_error` is set, tag every record with an `agent_run_id` so concurrent runs cannot collide on a CLI id, and have the sink close any still-open call as `abandoned` on shutdown. Records gain fields, none change shape or meaning, so the two consumers (cockpit describeEntry, --progress formatter) render them unchanged.
Owner
|
Merged — thank you! Squash commit 37a1b1a, CI green (verify + ledger). Verified independently before merging: checked out the PR head and ran test/agent-stream.test.js 17/17 locally, and reviewed the diff (additive record fields only, 5 new tests, docs updated). The pairing fix is exactly what the issue described. 🙏 |
This was referenced Nov 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
tool_resultline now becomes atool_endfor the call it answers, carryingstatus: "error"when the CLI setsis_error.tool_call_id—tool_use.id/tool_use_idon Claude,item.idon Codex.agent_run_id, so two runs streaming into one log stay distinguishable even when a CLI hands out the same tool-call id twice.close()closes any call that is still open asstatus: "abandoned", so a killed run no longer leaves a call that looks like it is still running.Nothing downstream changed. The cockpit view and the
--progressformatter render records generically, so the new fields are additive for both.Why
Fixes #182. A Claude Code run produced
tool_startrecords with no matchingtool_end: the adapter treated theuserline as output echo, which is wherestream-jsondeliverstool_resultblocks. The result itself carries onlytool_use_id, so recording the CLI's id was a prerequisite for pairing anything at all — and it also lets a log separate two concurrent runs instead of collapsing them when the CLI reuses an id.I did not infer the tool name inside the mapper, because a
tool_resultnever names the tool; the sink remembers thetool_useit saw, and an unmatched result still lands as a readabletool_endwithtool: nullrather than being dropped. I also did not add a read-side join pass: the log has to carry ids for any consumer to pair calls, and a post-processing pass would still have no way to tell a killed call from a slow one. Theabandonedstatus comes from the sink because it is the only place that knows a run ended.The 300s default agent timeout is the case that motivated the last part: the CLI never emits its final result, so without closing open calls on shutdown the log claims a tool ran forever.
Tests
npm testrun locally — everything passes exceptswebench harness isolation (evidence diff filter + per-run uv cache + predictions), which fails identically on unmodifiedmain(405ffa5) on this machine for want ofuv/docker. That failure is pre-existing and environmental, not related to this change.test/agent-stream.test.js, all driven by the existingclaude.ndjsonfixture: result pairing,is_errormapping toerror, an orphan result staying readable, shutdown closing an open call asabandoned, and two concurrent sinks not colliding on the same CLI id.node --test test/agent-stream.test.js→ 17 pass, 0 fail.npm run drift:scan— not applicable: no workflow or atom changed.AI assistance
Written with an AI coding agent (DeepSeek Harness) working from my own read of the streaming path. I reviewed the full diff and ran the suite locally before opening; the one failing test was confirmed pre-existing by re-running it against unmodified
main.