Skip to content

refactor(runtime): parse codex exec --json streams into agent events - #6924

Merged
waynesun09 merged 5 commits into
mainfrom
codex-runtime-stream-parser
Sep 3, 2026
Merged

refactor(runtime): parse codex exec --json streams into agent events#6924
waynesun09 merged 5 commits into
mainfrom
codex-runtime-stream-parser

Conversation

@waynesun09

Copy link
Copy Markdown
Member

Summary

Parses the codex exec --json JSONL stream (Codex CLI 0.152.1) into fullsend AgentEvents and RunMetrics, mirroring pi_progress.go. New files only.

  • parseCodexStream (one ResultEvent per call, like parsePiStream), applyCodexMetrics, structural capture detection (isCodexStreamCapture), codexStreamVerdict / parseCodexTranscriptFile for the tee'd capture, fixtures under testdata/codex/ (one real capture from npx @openai/codex@0.152.1 plus hand-authored failed / error / malformed / truncated / multi-turn / unknown-type streams), regen.sh, README.
  • Wire facts learned from the live capture, which corrected the plan: turn.completed.usage is the thread's cumulative total (replace, never sum; deltas emitted per turn); the item type is a sibling of id (no details object); only turn.failed is fatal — error items and non-terminal top-level errors emit no event (they stay in output.jsonl); a turn with no terminal event is incomplete even when codex exits 0; turn.started resets the terminal state so multi-turn tees are judged by their last turn.
  • Tool mapping: command_executionBash, file_change add → Write / update|delete → Edit (one event per changed path), mcp_tool_callmcp__<server>__<tool>, web_searchWebSearch, collab_tool_callAgent.

Review rounds

sol + Grok: HIGH (terminal state inherited across turns) and both MEDIUMs (failed turns counted zero turns; warnings rendered as failures) fixed in the second commit, plus structural detection, delta floor, regen hygiene.

Squash title: refactor(runtime): parse codex exec --json streams into agent events (the first commit says feat; per COMMITS.md a parser is not user-recognisable capability).


Part of a five-PR stack for #6920 (Codex as an agent runtime): A image pin → B stream parser → C OpenAI credential seeder → D runtime core (ADR 0099) → E enable + docs. Each PR is reviewable on its own diff; they merge bottom-up. Plan and verified Codex facts: research/fullsend-codex-runtime-plan.md in the ai-workspace-public research repo (to be linked once pushed).

Refs #6920

Assisted-by: Claude (implementation and review orchestration), Codex gpt-5.6-sol (review), Grok 4.6 (review)

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Parse Codex JSON streams into normalized runtime events

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Parses Codex JSONL streams into normalized agent events, metrics, and transcript verdicts.
• Maps completed Codex items while preserving multi-turn terminal and cumulative usage semantics.
• Adds live and synthetic fixtures covering tools, failures, malformed input, and truncation.
Diagram

graph TD
  A["Codex JSONL"] --> B["Capture Detection"] --> C["Stream Parser"] --> D["Item Mapping"] --> E["Agent Events"] --> F["Run Metrics"]
  C --> G["Terminal Verdict"]
  E --> G
Loading
High-Level Assessment

The current approach is appropriate: it mirrors the established Pi parser contract, uses typed wire structures with raw-item dispatch for forward compatibility, and emits the existing runtime-neutral event vocabulary. A generic map-based decoder would reduce declarations but weaken schema validation and make the nuanced terminal and usage semantics harder to review.

Files changed (13) +1822 / -0

Refactor (1) +709 / -0
codex_progress.goAdd Codex JSONL event parser and transcript verdict logic +709/-0

Add Codex JSONL event parser and transcript verdict logic

• Introduces typed Codex wire structures and parses completed items into normalized text, thinking, tool, token, error, and result events. Handles cumulative usage, multi-turn terminal state, malformed input, secret-safe summaries, metrics, structural capture detection, and transcript verdicts.

internal/runtime/codex_progress.go

Tests (10) +922 / -0
codex_progress_test.goCover Codex parsing, metrics, redaction, and verdict behavior +854/-0

Cover Codex parsing, metrics, redaction, and verdict behavior

• Adds comprehensive tests for live and synthetic streams, every supported item mapping, cumulative usage deltas, terminal-state transitions, malformed input, summary redaction, capture detection, and transcript classification.

internal/runtime/codex_progress_test.go

basic_run.jsonlAdd live successful Codex stream capture +10/-0

Add live successful Codex stream capture

• Provides a real Codex 0.152.1 capture containing reasoning, assistant messages, command execution, file creation, and final usage.

internal/runtime/testdata/codex/basic_run.jsonl

critical_error_only.jsonlAdd incomplete stream with parked critical error +3/-0

Add incomplete stream with parked critical error

• Models a top-level authorization error followed by no terminal event, validating incomplete-run classification and error preservation.

internal/runtime/testdata/codex/critical_error_only.jsonl

error_event.jsonlAdd non-fatal warning and error stream fixture +6/-0

Add non-fatal warning and error stream fixture

• Models error items and a top-level error followed by successful completion, ensuring warnings do not fail or failure-highlight the run.

internal/runtime/testdata/codex/error_event.jsonl

malformed_line.jsonlAdd malformed JSONL recovery fixture +8/-0

Add malformed JSONL recovery fixture

• Includes garbage, empty, and partially written lines before valid events to verify parsing continues through corrupt records.

internal/runtime/testdata/codex/malformed_line.jsonl

mcp_and_file_change.jsonlAdd comprehensive Codex tool mapping fixture +16/-0

Add comprehensive Codex tool mapping fixture

• Covers file additions, updates, deletions, failed patches, MCP calls, web search, collaboration, todo lists, and command status variants.

internal/runtime/testdata/codex/mcp_and_file_change.jsonl

second_turn_unfinished.jsonlAdd unfinished second-turn regression fixture +6/-0

Add unfinished second-turn regression fixture

• Models a completed first turn followed by an unterminated second turn, ensuring prior success is not inherited by the final verdict.

internal/runtime/testdata/codex/second_turn_unfinished.jsonl

truncated.jsonlAdd mid-record truncation fixture +5/-0

Add mid-record truncation fixture

• Ends during an item record without a terminal event to verify partial data is retained while the run is classified as incomplete.

internal/runtime/testdata/codex/truncated.jsonl

turn_failed.jsonlAdd failed turn and command fixture +6/-0

Add failed turn and command fixture

• Models a failed command followed by turn.failed, validating failure events, bounded diagnostics, and failed-turn accounting.

internal/runtime/testdata/codex/turn_failed.jsonl

unknown_types.jsonlAdd forward-compatibility fixture for unknown events +8/-0

Add forward-compatibility fixture for unknown events

• Includes unknown top-level and item types alongside valid events to ensure unsupported additions are skipped without aborting parsing.

internal/runtime/testdata/codex/unknown_types.jsonl

Documentation (1) +109 / -0
README.mdDocument Codex fixtures and verified wire semantics +109/-0

Document Codex fixtures and verified wire semantics

• Documents fixture origins, Codex 0.152.1 event structures, regeneration requirements, and behavioral details not apparent from the wire structs.

internal/runtime/testdata/codex/README.md

Other (1) +82 / -0
regen.shAdd reproducible live fixture capture script +82/-0

Add reproducible live fixture capture script

• Captures a sandboxed Codex JSONL run using the pinned CLI version, replaces machine-specific workspace paths, and verifies sanitization before retaining the fixture.

internal/runtime/testdata/codex/regen.sh

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 6:54 PM UTC · Ended 7:03 PM UTC

Commit: 00b99b9 · View workflow run →

@qodo-code-review

qodo-code-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Token subsets counted twice ✓ Resolved 🐞 Bug ≡ Correctness
Description
Codex input/output totals are emitted alongside their cached/reasoning subsets as independent
normalized counters, so the renderer double-counts those tokens. The live fixture represents 41,615
total tokens, but the emitted TokensEvent renders a total of 83,000 and RunMetrics similarly lose
cross-runtime comparability.
Code

internal/runtime/codex_progress.go[R461-465]

+			InputTokens:              usage.InputTokens,
+			OutputTokens:             usage.OutputTokens,
+			ReasoningTokens:          usage.ReasoningOutputTokens,
+			CacheCreationInputTokens: usage.CacheWriteInputTokens,
+			CacheReadInputTokens:     usage.CachedInputTokens,
Relevance

●●● Strong

Recent runtime precedent accepts correcting inconsistent token normalization and preserving
comparable metrics across parsers.

PR-#6147
PR-#3186

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The live capture's cache counters nearly exactly partition input (27386 + 13925 + 9 = 41320), while
reasoning is part of output; nevertheless, the parser copies all raw totals into separate normalized
fields. EventRenderer explicitly adds input, output, reasoning, cache-read, and cache-write
together, proving these normalized fields are expected to be disjoint.

internal/runtime/testdata/codex/basic_run.jsonl[10-10]
internal/runtime/codex_progress.go[340-347]
internal/runtime/codex_progress.go[456-466]
internal/runtime/renderer.go[76-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Codex `input_tokens` includes cached/cache-write input and `output_tokens` includes reasoning output, but the normalized event model treats cache and reasoning as separate counters. Subtract those subsets from input/output, flooring malformed negative results at zero, for both TokensEvent deltas and ResultEvent totals.

## Issue Context
The live fixture has input=41320 with cache-read+cache-write=41311, and output=295 with reasoning=74. The renderer sums every normalized category, so forwarding all raw values reports 83000 rather than 41615 total tokens.

## Fix Focus Areas
- internal/runtime/codex_progress.go[340-349]
- internal/runtime/codex_progress.go[456-466]
- internal/runtime/codex_progress_test.go[63-113]
- internal/runtime/codex_progress_test.go[390-423]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Token rebound duplicates usage ✓ Resolved 🐞 Bug ≡ Correctness
Description
After flooring a shrinking cumulative snapshot's delta to zero, emitTokensDelta still lowers the
reported baseline; if the snapshot later recovers, already-reported tokens are emitted again. For
example, cumulative input snapshots 500→300→500 produce incremental events totaling 700 even though
the final usage is 500.
Code

internal/runtime/codex_progress.go[R348-349]

+		reported = usage
+		onEvent(delta)
Relevance

●●● Strong

Accepted token-accounting precedents favor fixing cumulative snapshot regressions that duplicate
reported usage.

PR-#3186
PR-#6147

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
TokensEvent is defined as incremental usage, while the parser floors negative differences but
unconditionally assigns the smaller snapshot to reported. The existing shrinking-snapshot test
verifies only the immediate zero delta and therefore misses the duplicate delta produced by a
subsequent recovery.

internal/runtime/event.go[46-55]
internal/runtime/codex_progress.go[330-349]
internal/runtime/codex_progress_test.go[340-365]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When a cumulative usage field decreases, its emitted delta is correctly floored to zero, but the baseline is then reset to the lower value. A later recovery emits the difference again and violates TokensEvent's incremental-counter contract.

## Issue Context
Track the greatest reported value independently for each usage field, or otherwise ensure a decrease cannot lower the delta baseline. Add a three-snapshot regression test such as 500→300→500 and assert that emitted deltas sum to the final cumulative total.

## Fix Focus Areas
- internal/runtime/codex_progress.go[330-349]
- internal/runtime/codex_progress_test.go[340-365]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 65 rules
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 13/18, lines 1822/200; both must reach the floor). Router rationale: This adds a substantial new runtime parser with many independent event mappings, stream-verdict edge cases, metrics semantics, redaction paths, capture detection, and supporting scripts, creating a high density of subtle defects that benefit from redundant review passes.

Grey Divider

Tip of the day
💡 Did you know, you can add REVIEW.md to your repo root and Qodo follows it on every PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread internal/runtime/codex_progress.go Outdated
Comment thread internal/runtime/codex_progress.go Outdated
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:05 PM UTC · Ended 7:10 PM UTC

Commit: 9867b2e · View workflow run →

@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.65248% with 32 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/runtime/codex_progress.go 88.65% 16 Missing and 16 partials ⚠️

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:11 PM UTC · Ended 7:17 PM UTC

Commit: 86d89f6 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:19 PM UTC · Completed 7:35 PM UTC

Commit: d981fe6 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $8.14

@fullsend-ai-review fullsend-ai-review Bot added the risk/moderate PR risk: moderate label Sep 2, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

Risk Assessment: moderate (2/5)

Details

Moderate risk: large additive PR (13 files, 1935 lines) but all newly added files with no modifications to existing code, no protected paths, no dependency changes, no CI changes. The high change-size signal (blast=large) is offset by low scores across all other Tier 1 dimensions. All files are new so rollback is trivial. Tier 1 signals unchanged from prior assessment; score preserved at 2.

Previous run

Risk Assessment: moderate (2/5)

Details

Moderate risk: large additive PR (13 files, 1935 lines) but all newly added files with no modifications to existing code, no protected paths, no dependency changes, no CI changes. The high change-size signal (blast=large) is offset by low scores across all other Tier 1 dimensions. All files are new so rollback is trivial. Score unchanged from prior assessment.

Previous run (2)

Risk Assessment: moderate (2/5)

Details

Moderate risk: large additive PR (13 files, 1935 lines) but all newly added files with no modifications to existing code, no protected paths, no dependency changes, no CI changes. The high change-size signal (blast=large) is offset by low scores across all other Tier 1 dimensions. All files are new so rollback is trivial. Score unchanged from prior assessment.

Previous run (3)

Risk Assessment: moderate (2/5)

Details

Moderate risk: large additive PR (13 files, 1917 lines) but all newly added files with no modifications to existing code, no protected paths, no dependency changes, no CI changes. All files are new so rollback is trivial. The security label on the linked issue elevates Tier 3 but the change is a well-scoped stream parser within a planned 5-PR stack.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

Looks good to me

Previous run

Review

Findings

Low

  • [stale-reference] internal/runtime/codex_progress.go:18 — The block comment says "basic_run.jsonl is a live capture" but the fixture file is named basic_run.ndjson. Every other reference in the codebase (tests, README, regen.sh) correctly uses .ndjson. This comment is the only remaining stale reference.
    Remediation: Change basic_run.jsonl to basic_run.ndjson in the block comment at line 18.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

Low

  • [stale-reference] internal/runtime/codex_progress.go:25 — The block comment says "basic_run.jsonl is a live capture" but the fixture file was renamed to basic_run.ndjson (to match the pi/opencode convention, as recommended by the prior review). All other references (test file, regen.sh, README.md) correctly use .ndjson. This comment is the only remaining stale reference.
    Remediation: Change basic_run.jsonl to basic_run.ndjson in the block comment at line 25.

Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

Medium

  • [naming-convention] internal/runtime/testdata/codex/ — Fixture files use .jsonl extension, but both existing testdata directories (testdata/pi/ and testdata/opencode/) consistently use .ndjson for the same kind of NDJSON fixture files. The formats are identical; the extension difference breaks the convention.
    Remediation: Rename all fixture files under testdata/codex/ from .jsonl to .ndjson (e.g., basic_run.jsonlbasic_run.ndjson) and update all references in codex_progress_test.go, regen.sh, and README.md.

Low

  • [string-convention] internal/runtime/codex_progress.go — Truncation uses ASCII "..." (three dots) in codexCapPath, codexOutputTail, and the web_search handler, but every other _progress.go file in the package uses the Unicode ellipsis U+2026 for the same purpose (claude_progress.go lines 408, 434, 454; pi_progress.go line 118). This creates a visible inconsistency in the renderer's output between runtimes.
    Remediation: Replace "..." with the Unicode ellipsis character "..." in codexCapPath, codexOutputTail (the prefix form), and the web_search truncation.

  • [edge-case] internal/runtime/codex_progress.go:263 — In codexCommandSummary, when status is "failed" and ExitCode is non-nil, the suffix is unconditionally overwritten from "failed" to "exit N". If a command has status="failed" but exit_code=0 (e.g., killed by signal, timeout, or sandbox rejection after the process exited), the summary shows "exit 0" which loses the failure indication. The "completed" branch correctly guards with *item.ExitCode != 0.
    Remediation: Preserve the failure label: suffix = fmt.Sprintf("failed (exit %d)", *item.ExitCode) for the failed case, or only override when *item.ExitCode != 0.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:55 PM UTC · Ended 8:00 PM UTC

Commit: e6146ba · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure (validation failed after 2 iteration(s)) · Started 8:02 PM UTC · Completed 8:44 PM UTC

Commit: 436ad37 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high

@ralphbean ralphbean self-assigned this Sep 2, 2026
@waynesun09
waynesun09 force-pushed the codex-runtime-stream-parser branch from 436ad37 to a128b87 Compare September 2, 2026 22:27
fullsend-ai-review[bot]

This comment was marked as outdated.

Base automatically changed from codex-runtime-image-pin to main September 2, 2026 23:17
@waynesun09
waynesun09 force-pushed the codex-runtime-stream-parser branch from a128b87 to 28fd77c Compare September 2, 2026 23:17
Adds parseCodexStream, the JSONL parser for `codex exec --json`, mapping
codex's ThreadEvent stream onto the runtime-neutral AgentEvent vocabulary
so the codex runtime adapter can drive the same renderer, metrics and
exit-code override as Claude Code and pi.

New, self-contained files only (no existing file is touched), so this sits
under the image-pin PR without conflicting:

  * internal/runtime/codex_progress.go — parseCodexStream, applyCodexMetrics,
    isCodexStreamCapture, codexStreamVerdict / parseCodexTranscriptFile.
  * internal/runtime/codex_progress_test.go — table-driven over every fixture.
  * internal/runtime/testdata/codex/ — fixtures, regen.sh and a README
    carrying the event struct list.

Mapping: command_execution -> Bash, file_change -> Write (add) / Edit
(update, delete) per change, mcp_tool_call -> mcp__<server>__<tool>,
web_search -> WebSearch, collab_tool_call -> Agent, agent_message -> text,
reasoning -> thinking, todo_list ignored. Summaries are redacted before they
are truncated, and only failed or declined calls surface their output.

Three behaviours were verified in codex's own event processor rather than
assumed from the struct definitions, and drive the verdict logic:

  * turn.completed.usage is the thread's *cumulative* usage, not the turn
    delta, so successive values replace each other instead of summing.
  * an `error` item is a warning (config warning, deprecation notice, model
    reroute) and a top-level `error` event is parked as last_critical_error
    with the run still going — neither fails the run on its own.
  * an interrupted turn emits no terminal event at all, and `codex exec` can
    exit 0 regardless, so a stream with no terminal event is reported as an
    incomplete failure.

basic_run.jsonl is a live capture from @openai/codex@0.152.1 on gpt-5.6-luna
with the working directory redacted; the rest are hand-authored to the
rust-v0.152.1 structs to cover shapes a happy path never produces.

TotalCostUSD and Model stay the runner's to fill: the stream carries neither.

Refs #6920

Assisted-by: Claude (implementation)
Signed-off-by: Wayne Sun <gsun@redhat.com>
…ring as failures

Codex and Grok review findings on the stream parser.

**The verdict could inherit a previous turn's outcome.** `terminal` recorded
the last terminal event but was never cleared, so turn.completed →
turn.started → EOF reported a run that died mid-second-turn as a clean
finish. A turn.started now reopens the outcome — the previous terminal event
described that turn, not this one — in both directions, and whichever
terminal event arrives last decides. A turn.failed also counts toward
NumTurns now: it consumed a prompt and did work. It carries no usage on the
wire (only turn.completed does), so the token counters keep the last
completed turn's snapshot.

`codex exec` shuts down after its first completed turn today, so this is not
reachable through the runner yet; it is through `codex exec resume` and
through anything that tees several turns into one file, which
ParseTranscriptFile will be handed.

**Warnings must not render as failures.** The renderer prints every
ErrorEvent with StepFail, so emitting one for a codex `error` item — which
the processor produces for config warnings, deprecation notices and model
reroutes — painted red failure lines across runs that succeeded. The same
went for a non-terminal top-level `error`, which the processor parks and
carries on from. Neither emits an AgentEvent now: AgentEvent has no
informational kind, and RetryEvent promises an attempt/limit/delay these do
not have. Both stay in output.jsonl, which is kept as a run artifact, and
the top-level message is still parked as the reported reason when the stream
then ends without a terminal event. turn.failed still emits an ErrorEvent —
that one is a real failure.

**Capture detection was a substring scan.** The claim that JSON escaping
made it safe held only for a marker quoted in text; one nested as a real key
in another envelope, {"payload":{"type":"turn.completed"}}, still matched.
Detection is structural now: a line is unmarshalled and its top-level type
checked against the ThreadEvent set. A bare top-level "error" is in the set
but does not decide on its own, being far too generic to identify a codex
stream. pi's isPiStreamCapture has the same shape of gap with far less
exposure; noted in a comment rather than changed here.

That corrected a test assertion written against the old scan: a real
item.updated line whose agent text quotes "turn.completed" *is* a codex
capture, and structural detection says so.

Smaller hardening: token deltas floor at zero, so a non-monotonic usage
snapshot cannot report negative tokens; a failed file_change that never
named a path still reports one failed tool call instead of nothing; MCP
server and tool names are redacted before the mcp__<server>__<tool> name is
built, since they reach CI annotations; regen.sh installs with
--ignore-scripts and replaces the working directory literally, so a temp
path holding regex metacharacters cannot corrupt the fixture; and the live
fixture's thread id is asserted by shape, so re-capturing it needs no test
edit.

Documented, not changed: ToolCalls counts one per changed path in a
file_change item, because a single apply_patch touching N files is N edits —
what Claude Code's per-file Edit/Write calls would count — so the metric
stays comparable across runtimes. Assistant text is passed through
unredacted, as on pi and Claude Code; the renderer sanitizes it for display.

Refs #6920

Assisted-by: Claude (implementation), Codex gpt-5.6-sol (review), Grok (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
…eline dropping

Qodo review of the codex stream parser. Two counting bugs, both of which
inflated reported usage.

Codex nests its usage categories, following the OpenAI Responses API:
input_tokens is the whole input *including* the cached and cache-write
parts, and output_tokens is the whole output *including* reasoning. Claude
Code and pi report the opposite — Anthropic's convention, where cache and
reasoning are disjoint from input and output — and that is what RunMetrics
means and what the renderer sums for its total. The parser passed codex's
numbers through unchanged, so every cached and reasoning token was counted
twice: the live fixture's 41,615 real tokens rendered as ~83,000. The
arithmetic is visible in the fixture itself — 27,386 cached plus 13,925
cache-write against an input_tokens of 41,320, nine tokens of genuinely new
input. codexUsage.counters() now subtracts the subsets, flooring at zero,
and the test asserts the five normalized counters sum to 41,615.

Second, emitTokensDelta floored a shrinking snapshot's delta at zero but
still moved the baseline down to it, so the next increase was measured from
the smaller value and counted again: 500 -> 300 -> 500 emitted 700 tokens
for a thread that used 500. The snapshot is now a per-field high-water mark,
which makes every delta non-negative by construction rather than by
clamping, and the ResultEvent reports that mark rather than the last
snapshot. The three-snapshot case is a test.

Both are properties of the wire format rather than of any one run, so the
testdata README records them next to the cumulative-usage note.

Refs #6920

Assisted-by: Claude (implementation), Qodo (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
…r runtimes

Review bot findings on the codex stream parser, all three about consistency
with the runtimes that came before it.

The fixtures were `.jsonl` while testdata/pi and testdata/opencode use
`.ndjson` for exactly the same thing. Renamed, with the references in the
test, regen.sh and the README moved with them. The runtime's own tee'd
artifact stays `output.jsonl` — that name is the runner's contract, not a
fixture convention.

The truncation markers were three ASCII dots; claude_progress.go and
pi_progress.go both use the single U+2026 ellipsis. Switched, and the length
assertions now expect one rune rather than three.

The third is a display bug rather than a convention. A `failed`
command_execution whose exit_code was present had its label *replaced* by
"exit N", so an item that failed while reporting exit 0 rendered as
"$ cmd (exit 0)" — indistinguishable from a success. The status is the
finding and the code only qualifies it, so it now reads "failed (exit N)",
with the exit-0-but-failed case in the table tests.

Refs #6920

Assisted-by: Claude (implementation), fullsend review bot (review)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:20 PM UTC · Completed 11:35 PM UTC

Commit: 1518000 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $4.49

fullsend-ai-review[bot]

This comment was marked as outdated.

The block comment in codex_progress.go still called the live capture
basic_run.jsonl after the fixtures moved to the .ndjson convention.

Refs #6920

Assisted-by: Claude (implementation)
Signed-off-by: Wayne Sun <gsun@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:46 PM UTC · Completed 11:59 PM UTC

Commit: dc5cdc0 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $2.55

@waynesun09
waynesun09 dismissed stale reviews from fullsend-ai-review[bot], fullsend-ai-review[bot], and fullsend-ai-review[bot] September 2, 2026 23:53

Outdated: the findings were addressed in later commits (fixture names, failed-command label, token accounting, stale comment) and every thread is resolved; dismissed so the stack can enter the merge queue.

@waynesun09
waynesun09 added this pull request to the merge queue Sep 2, 2026
@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Sep 2, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 3, 2026
@waynesun09
waynesun09 added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit 848714e Sep 3, 2026
66 of 67 checks passed
@waynesun09
waynesun09 deleted the codex-runtime-stream-parser branch September 3, 2026 00:19
@fullsend-ai-retro

fullsend-ai-retro Bot commented Sep 3, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 12:21 AM UTC · Completed 12:37 AM UTC

Commit: dc5cdc0 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $7.02

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #6924 — Codex stream parser

PR #6924 was a large additive PR (+1935 lines, 13 new files) implementing a Codex JSONL stream parser (parseCodexStream) for the fullsend runtime. Part of a five-PR stack for #6920. The PR merged successfully after all real bugs were fixed.

Key finding: correctness sub-agent dimension drift

The fullsend review bot's correctness sub-agent (opus-tier) drifted entirely into convention/style review on its first successful pass (run 33672393449, $8.14). Despite receiving explicit verification checklist items for the token-counting arithmetic (highWater logic, emitTokensDelta logic), it spent its tool budget reading pi_progress.go, opencode_progress.go, and claude_progress.go for naming-convention comparison. It produced 6 findings — all style/convention (naming-convention, string-convention, test-helper-convention, cross-runtime-reuse, code-organization) — and zero correctness findings.

Meanwhile, qodo-code-review found both critical bugs in the same code on its single pass:

  • HIGH: Token subsets counted twice — input_tokens includes cached tokens and output_tokens includes reasoning tokens, but the normalizer treated them as additive, inflating ~41,615 real tokens to ~83,000.
  • MEDIUM: Token rebound duplicates usage — after flooring a negative delta to zero, the baseline still dropped, causing re-emission of already-reported tokens (500→300→500 produced 700 instead of 500).

Both bugs were fixed by the author before human review.

Evidence for existing issues

  • #2836 (scope constraints for sub-agents): This retro provides a concrete failure case — the correctness sub-agent's scope includes convention-adjacent items (removal/rename staleness, cross-file verification) that created a natural on-ramp to pure convention review. Scope constraints would have prevented the drift.
  • #6129 / #6179 (WIF 429 / merge queue flakes): Both PRs chore(#6920): pin Codex CLI 0.152.1 in the sandbox image, add a codex runtime stub #6923 and refactor(runtime): parse codex exec --json streams into agent events #6924 in the stack needed second merge-queue attempts due to WIF pool 429 errors.
  • #6922 (Sonnet 4.5 unavailable on Vertex): The risk-assessment sub-agent failed on its first attempt because claude-sonnet-4-5@20250929 was not available on Vertex. The orchestrator retried without specifying a model, which succeeded.
  • #6911 (delta review disabled): Review runs 6 and 7 both found the identical stale .jsonl comment reference, suggesting delta review was not active. If delta review had been working, run 7 should have scoped to the incremental diff.
  • #6936 (cancelled run telemetry): 3 review runs were cancelled due to rapid pushes; their cost/token data is not captured.

Workflow summary

Stage Detail
Triage Agent ran ($0.84), classified as medium/feature
Code No code agent — author implemented manually
Review (fullsend) 8 runs: 3 cancelled, 1 failed (validation), 4 successful ($20.92 total). Found convention/edge-case issues but missed both correctness bugs.
Review (qodo) 1 run. Found both critical token-counting bugs (HIGH + MEDIUM).
Review (human) ralphbean approved at commit 436ad37 (bugs already fixed, no comments).
Merge Second merge-queue attempt (WIF 429 flake on first).

Proposals filed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge risk/moderate PR risk: moderate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants