Repository navigation
fix(transcripts): read_agent_transcript reads Codex code-mode commands and patches - #1368
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nscript (#1362) Since code mode (0.144+) Codex runs commands and patches through custom_tool_call(name="exec") scripts. The reader only knew function_call, so a Codex child's timeline, shell_commands and file_changes were mostly empty. The exec-script grammar moves from the renderer adapter to src/shared/codex/execScript.ts so both read scripts the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Disposition, review round 1 (a: MERGE-READY; b and c: FIX-BEFORE-MERGE) and steering q83.
|
|
Disposition, verification (b and c: FIX-BEFORE-MERGE on the lexer's regex-versus-division guesses). The original false-call blocker is confirmed fixed by both.
|
…ription; pin interpolation and nested calls (#1368 verification 2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Disposition, verification 2 (a and b: MERGE-READY; c: FIX-BEFORE-MERGE, on items c labels residuals).
|
…xecuted: unknown (#1368 steering q86) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Disposition, steering q86 (the manager's adjudication of c's FIX-BEFORE-MERGE on the static-reading contract). The narrowed contract is accepted on the condition that the uncertainty is carried on the output entries themselves. Implemented in
|
…ries distinct (#1368 final check c) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-codex-custom-calls
|
Disposition, final check c (FIX-BEFORE-MERGE: de-duplication ignored |
…-codex-custom-calls
Fixes #1362
Problem
read_agent_transcriptshowed almost nothing a modern Codex agent did. Since code mode (Codex 0.144 and later), commands and patches run throughcustom_tool_call(name="exec"). The call's input is a JavaScript program that callstools.exec_command({cmd: "…"}),tools.apply_patch(…), MCP tools and so on. The reader handled onlyfunction_call/function_call_output, so a Codex child'stimeline,shell_commandsandfile_changesheld its messages and nothing it ran.The owner's corpus (2,457 rollouts) has 98,327
execscripts and 7,538 top-levelapply_patchcustom calls. Theexec_commandarguments are JS object literals, and 73,789 of 87,184 are not JSON.Change
execgrammar moves unchanged fromproviders/codex/renderer/adapters/command.tstosrc/shared/codex/execScript.ts:matchingCallEnd,parseExecCommandArgumentand the literal decoders;codexExecScriptCalls(script), which lists everytools.<name>(…)call in source order. Calls are found with anacornparse; acorn is already a runtime dependency and the renderer's embedded-operation adapter parses the same scripts. So text that merely quotestools.x(in a comment, string, template or regex is never a call, and regex-versus-division is decided by the grammar (verification b/c). A script that does not parse (98 of 99,256 recorded) falls back to a lexical scan, since it never ran as written;execscripts, in source order:exec_commandargument decodes (92% of calls): oneshell_commandper call, with itscwd;{cmd, …}over a mapped array, concatenation, a template): oneshell_commandwhose command is the script itself. A partial list would silently drop the unreadable commands, which is this bug again, and a guessed command is worse;apply_patch: onepatchitem for the script, naming the files from the patch headers. They are decoded from a quoted argument, or read from the script text for templates and variables. An interpolated header (*** Update File: ${path}) resolves only through oneconst path = "literal"binding; otherwise the path is dropped, never shown as${path}(review a, steering q83);web__run,ALL_TOOLSqueries): atool_readforexec, so the timeline still shows that a script ran.apply_patchcustom call becomes apatchitem;custom_tool_call_outputbecomes the same raw-output item asfunction_call_output, still hidden unlessinclude.rawToolOutputs.Plan:
docs/plans/2026-09-27-codex-custom-call-transcript-reader.md. Its evidence section has the corpus counts.Tests (4 reader tests red on main, plus grammar unit tests)
codex-0.157-custom-calls.jsonl: a slice of a recorded 0.157.1 rollout with one script of each census shape (a single command, two commands, a computed argument, a patch, a non-command script), three outputs, the prompt and the final answer.session_metais left out.shell_commands, raw outputs andinspectstats.codex-custom-call-forms.jsonl: six single recorded records. Provenance is intesting/fixtures/agent-transcripts/README.md.apply_patch;tools.exec_command(...); the old regex scan turned it into a command;tool_readtarget;${path}patches around a command; this fails with${path}if the resolution is removed;if (false)command and patch appear intimeline,shell_commandsandfile_changesonly withexecuted: "unknown". Removing the marker fails the test.src/shared/codex/execScript.test.tscovers the grammar edges from verification b and c (a regex afterif (…)and afterawait, division after}) and the recordedtools["name"](…)form. These fail with the lexer alone.Verification
ed86c13c(0 behind main):npx tsc -bis clean.src/main/agentTranscripts,src/providers/codex/renderer,src/shared/codexand the two MCP transcript-tool suites pass 218/218.tools["name"]calls only the parse sees. The parse costs 4.2 s over the whole corpus, 36 ms at worst.src/providers/shared/renderer+src/providers/codex: 358/361 in one batch at load average 147. The three failures were 8–11 s timeouts insystemic-findings.renderer.test.tsx, which passes 6/6 alone.Review (3 reviewers, round 1)
tools.x(was read as a call. Fixed by the lexical scan in52a82f85, with a recorded test. Then, after verification b and c found regex-versus-division cases no lexer settles, fixed at the root in8955ff4bby parsing with acorn.d3bdffff: the${path}resolution, one item per script, the fixture README, and removal of an unpinned dead dispatch.shell_commandandpatchentry read from Codexexecscript source carriesexecuted: "unknown"on the entry itself. The reader lists the calls a script contains, read without running it.apply_patchcustom calls are not marked, since they are the call Codex made.agent_transcript_read_filedescription names the field. Nested calls are listed in source order.${path}resolution have no recording, and a re-assignedletbinding resolves to its first value (0 recorded);execcall with empty input gives an emptytool_read;Move toheaders and spacedtools . x (forms have no recording.Out of scope
call_id; the reader never pairedfunction_call_outputeither.write_stdinand MCP calls inside scripts are only named, as thetool_readtarget.🤖 Generated with Claude Code