fix(runtime): unwrap codex exec transcript in Start() Raw + cap synth judge answers (#724)#726
Merged
Merged
Conversation
… judge answers (#724) Two composing halves of the same live `skillopt synth` failure (2026-07-08): 1. codex Start().Raw was the full `codex exec --json` transcript (thread.started banner, turn events, reasoning items), not the assistant's answer — the codex flavor of the claude bug fixed in #722 (b53269f). Forked-session consumers (skillopt synth/ab) that parse Start().Raw as the assistant's answer saw the whole transcript and could not find the challenger item's context/question/ rubric. Fix at the adapter (same altitude as #722): reuse parseCodexJSONResult — the exact parser the Deliver path uses to build Summary — to surface the joined agent_message text as Raw, failing open to the raw stdout when the stream carries no agent_message (older CLI, plain-text fallback, unexpected shape). RuntimeRef is untouched. 2. synthJudgePrompt embedded both weak/strong answers verbatim with no cap. Even after the unwrap a genuinely long answer, combined with both halves embedded, blew ARG_MAX on the kimi judge exec. capSynthAnswer now truncates each embedded answer to synthMaxAnswerBytes (12KB) with a byte-accurate "[truncated N bytes]" marker, so judge prompts stay small regardless of runtime verbosity. Under-limit answers are embedded byte-identical with no marker. Tests: TestCodexStartUnwrapsJSONTranscript (adapter unit: agent_message text vs transcript fallback, RuntimeRef preserved); TestRealSkillOptABDeliverUnwrapsCodex- TranscriptForSynth (consumer regression through the real delivery seam, codex flavor of #722's claude test); TestSynthJudgePromptCapsAnswers (cap + marker + verbatim-under-limit + exact-at-limit). Closes #724 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
Two composing halves of the same live
skillopt synthfailure (2026-07-08), the codex sibling of #722.(1) codex
Start().Rawunwrap.CodexAdapter.Startrunscodex exec --jsonand returnedStartResult{Raw: result.Stdout}verbatim — the full JSONL transcript (thread.started banner, turn.started, reasoning items, agent_message, turn.completed usage), not the assistant's answer. The sole consumer ofStart().RawisrealSkillOptABDeliver's forked-session path (emptyRuntimeRef→ Start), which feeds skillopt synth (challenger + judge), ab, and the live-AB challenger. Those consumers parseRawas the assistant's answer, so they saw the whole transcript and could not find the challenger item'scontext/question/rubric. This is the exact codex flavor of #722's claude envelope leak.(2) synth judge-prompt hygiene.
synthJudgePromptembedded both weak/strong answers verbatim with no cap. Even after (1) unwraps the answer, a genuinely long answer combined with both halves embedded blew ARG_MAX on the kimi judge exec (issue evidence: run 2, items 1 & 3 died at the judge exec).Why
The judge scores answer quality against a rubric — it does not need a full runtime transcript. And a forked-session consumer that parses
Rawas the assistant's answer must receive the answer, not the CLI's event stream.How
internal/runtime/adapter.goCodexAdapter.Start: after parsing the thread id, reuseparseCodexJSONResult(the same parser the Deliver path uses to buildSummary) to surface the joinedagent_messagetext asRaw. Fail-open: fall back to the raw stdout when the stream carries noagent_message(older CLI, plain-text fallback, unexpected shape), soRawalways surfaces the underlying text and unwrap never errors.RuntimeRefis untouched. Same altitude/pattern as fix(runtime): unwrap claude CLI JSON envelope in Start() Raw #722 (b53269f).internal/cli/skillopt_synth.go: newcapSynthAnswercaps each embedded answer tosynthMaxAnswerBytes(12KB) with a byte-accurate[truncated N bytes]marker;synthJudgePromptwraps both weak and strong answers. Under-limit answers are embedded byte-identical (no marker).Additive and fail-open; no CLI surface change (internal adapter/prompt behavior, like #722 which shipped without docs changes).
How tested
Toolchain pinned:
export PATH=/root/.local/toolchains/go1.26.4/bin:$PATH, isolatedHERDR_SOCKET_PATH,HERDR_ENVunset.go build ./...→ exit 0go vet ./internal/runtime ./internal/cli→ exit 0go test -count=1 ./internal/runtime→ ok (0.081s)go test -race -count=1 ./internal/runtime→ ok (1.122s)go test -count=1 ./internal/cli→ ok (224.832s)New tests (all PASS):
TestCodexStartUnwrapsJSONTranscript— adapter unit: unwraps agent_message text, joins multiple messages, falls back to full stdout when no agent_message;RuntimeRefpreserved in every case. Uses a realcodex exec --jsontranscript shape.TestRealSkillOptABDeliverUnwrapsCodexTranscriptForSynth— consumer regression through the realrealSkillOptABDeliverseam with a realCodexAdapterover a fake runner (codex flavor of fix(runtime): unwrap claude CLI JSON envelope in Start() Raw #722'sTestRealSkillOptABDeliverUnwrapsClaudeEnvelopeForSynth); the delivered answer must be the agent_message andparseSynthGeneratedItemmust succeed.TestSynthJudgePromptCapsAnswers— cap + byte-accurate marker for oversized answers, verbatim under-limit, exact-at-limit not truncated.Closes #724
🤖 Generated with Claude Code