Repository navigation
Keep the event path honest: background subagents, subagent models, big events, shared logs, --no-claude decks, control markers - #1940
Merged
Conversation
…und subagent Claude Code puts an agent_id on every call a subagent makes and none on the main thread's, but the board handed every call naming nobody to the newest live subagent. With a background Task running, the root's own Read, Edit and Bash were drawn and counted on the subagent's card, and a root call still running when the subagent finished stayed in flight on a done card. Once a subagent of a session names itself on its own traffic, a call naming nobody is the root's, and the calls that reached a subagent before that are handed back. Sessions whose subagents never name themselves keep the old rule.
The server stamped the session's root model on every event that arrived without one, the subagent's own events included, and the board wrote it over the model the transcript named for that subagent. Nothing sent the right one again, so a Haiku subagent under an Opus session read and was priced as Opus. A subagent's events are now stamped with its own model when it is known and with nothing otherwise, and the board only takes a payload's model onto the agent the payload names.
Every deck refuses an ingest body over 5,000,000 characters, so an Edit of a file of a few megabytes, whose result carries the whole file, reached no deck at all and the turn's end settled the successful call as failed. The hook now cuts the largest fields of such an event down to a marker saying how big they were, until it fits under the cap, and keeps the event, session, tool, call id and cwd the deck settles the call by.
Only the owner of a shared events.jsonl may rotate it, and the owner only checked the file after writing a line of its own. A scoped deck on a quiet tree owns the default log and writes nothing, while the machine-wide deck beside it writes every line and may not rotate, so the 50 MB cap held for nobody. The owner now also looks on the 30-second floor the rotation already describes, whether or not it writes.
Starting a deck with --no-claude removes no hooks an earlier run installed, and the hook posted every Claude event to every deck whose workspace matched without reading the record's claude flag. The Codex-only deck drew every Claude session live, could be elected to log them, and lost them again at its next boot because its replay does honour the flag. The hook now skips a deck whose record says claude: false before the writer election, and the ingest answers a Claude-stamped event with a refusal on such a deck, so an older hook hands the log on rather than dropping the line.
Event names beginning with __ are the deck's own control markers, such as the one its Clear sends to every page. The hook ingest passed any name through, so such a marker could also arrive from outside the server and be written to the log. The ingest now refuses a reserved event name, and the boot replay honours a marker only when the server itself recorded it.
BarganConstantin
force-pushed
the
fix/event-path
branch
from
October 4, 2026 23:57
4a125c9 to
6cd3dc3
Compare
Closed
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 changes
agent_idon every call a subagent makes and none on the main thread's, but the board handed every call naming nobody to the newest live subagent. With a background Task running, the root's own Read, Edit and Bash were drawn and counted on the subagent's card, and a root call still running when the subagent finished stayed in flight on a done card. Once a subagent of a session names itself on its own traffic, a call naming nobody is the root's, and the calls that reached a subagent before that are handed back. Sessions whose subagents never name themselves (older Claude Code) keep the old rule.events.jsonlmay rotate it, and the owner only looked at the file after writing a line of its own, so a quiet scoped deck owning the log beside a busy machine-wide deck let it grow past 50 MB without bound. The owner now also looks on the existing 30-second floor.--no-claudedeck is not handed Claude sessions.--no-clauderemoves no hooks an earlier run installed, and the hook never read the record'sclaudeflag, so a Codex-only deck drew every Claude session live and lost them at its next boot. The hook now skips such a deck before the writer election, and the ingest refuses a Claude-stamped event on it so an older hook hands the log on instead of dropping it.__are the deck's own control markers. The hook ingest now refuses a reserved name, and the boot replay honours a marker only when the server itself recorded it.Verification
npm run typecheckclean.vitest run --maxWorkers=3 --minWorkers=1): 903 files, 11,777 tests passed, exit 0.background-subagent-root-calls.test.ts— before: the root's Read/Bash landed on the subagent (['Bash','Grep']on a done subagent, root held onlyTask).subagent-model-stamp.test.ts— before: the subagent's stamped events carriedclaude-opus-5and its card read Opus after ModelObserved said Haiku.hook-oversized-event.test.ts— runs the real hook against the server's real ingest handler; before: the deck answered 413 and no PostToolUse arrived.event-log-idle-rotation.test.ts— before: a 51 MB log owned by an idle deck was never rotated.no-claude-deck-ingest.test.ts— before: theclaude: falsedeck was posted the Claude event and its ingest answered 200.event-ingest-reserved-names.test.ts— before: the ingest answered 200 for a reserved name and the replay honoured a marker not recorded by the server.posttooluse-idempotent.test.ts(its last outcome now names nobody, like the call's own PreToolUse — naming a subagent there would now mean the session's subagents name themselves), andreplay-scope-696.test.ts(seeds__clearwith theinternalsource the deck has always written it with).AGENTS_DECK_NO_*), a session with a background subagent and transcripts on disk (root on Opus, subagent on Haiku), the root's Bash posted before the subagent's first own call: the subagent's detail reads DONE · Haiku 4.5 · 1 call (Grep) with nothing in flight; the root's reads Opus 4.5 · 4 calls (Task, Bash in flight, Read, Edit); when the Bash finishes it settles on the root.