Interactive codex spawn never connects: rollout is born at first turn, queued first message deadlocks on the transcript gate #96
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#96
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Symptom
Starting a Codex instance from the New-run page never "connects": the chat page shows "Waiting for the transcript…" indefinitely, and a first message typed into the New-run composer stays parked forever ("Sends when Codex is ready"). The codex TUI itself is healthy — it sits idle at its greeting in the run's tmux session.
Root cause (live-diagnosed 2026-07-10)
$CODEX_HOME/sessions/YYYY/MM/DD/rollout-*.jsonl) lazily at first-turn start, not at session init. Evidence: an issue-#87 spike session hassession_meta.payload.timestamp01:25:38Z but a file birth of 01:27:40Z — the rollout appeared only when the first message was sent; a 30-min-idle interactive instance had no rollout file at all and no rollout fd open.codex execwrites immediately (the turn starts at once), which is why the Tier-2 spike never surfaced this.SpawnSpec.InitialPrompt.LocateTranscript's cwd-match over the rollout tree → it misses forever for an idle TUI.Verified against ADR-0038 (#92/#94) and #93
The ReadChat refactor is behavior-neutral here: codex's
ReadChatwith an empty path composes the same idle empty chat core used to synthesize, the frontend gate and placeholder copy are unchanged, and the spawn path is untouched. The diagnosis holds on current main. The #93 dead-session sweep is orthogonal (the session is alive).Workaround until fixed
Type into the run chat composer despite the "Waiting for the transcript…" placeholder — sends work in that state; the first send births the rollout and the chat connects on the next poll. Or attach to the tmux session directly.
Proposed direction (maintainer-approved)
SpawnSpec.InitialPrompt— the AFK-proven trailing positional for both providers. Retires the client-side queue and its gate: kills the deadlock by construction, no cold-start keystroke race, text survives reloads.Agent brief follows in a comment.
Agent Brief
Category: bug
Summary: An interactive Codex spawn never connects because its transcript is only born at the first turn — deliver the operator's first message on the spawn argv, make the pre-transcript chat state honest, and pin the transcript-birth timing in the conformance bar.
Current behavior:
The New-run composer's text is parked client-side (issue #41) and auto-sent only once the run's transcript is located. codex-cli 0.133.0 creates its rollout transcript only when the first turn starts, so an interactive codex run with no message yet has no transcript: the locate misses forever, the chat page shows "Waiting for the transcript…", and the parked message never sends — a deadlock (the message that would create the transcript waits for the transcript). The parked text is lost on a tab reload. claude-code is unaffected only because its transcript file exists from process start, which made transcript availability double as a TUI-readiness proxy.
Desired behavior:
provider.SpawnSpec.InitialPrompt, which both adapters already emit as the trailing positional argument — present before the process starts, so there is no post-spawn keystroke and no cold-start race. The message therefore starts the first turn immediately, the transcript is born, and the chat connects without any operator intervention.InitialPromptstands).InitialPrompt.codex execwrites it immediately. The ADR-0036 live-spike checklist gains a "when is the provider's transcript born?" question so the next adapter cannot hit this silently.Key interfaces:
provider.SpawnSpec.InitialPrompt— already the trailing-positional seed prompt in both adapters'SpawnArgv; today only the AFK launcher populates it. The manual-launch path (launch spec + its HTTP create-instance surface) must be able to carry the operator's first message into it. Watch the existing invariant that injected spawn flags land BEFORE the trailing prompt.Acceptance criteria:
InitialPromptstays green).Out of scope:
LocateTranscript's cwd-match or reading codex's internal state DB to locate sessions earlier.Land-PR audit — PR #97 → PASS
Verification signal relied on: the green
ci / nativecheck run (exit 0,success, 4m48s). That single job exercises the full stack this diff touches — SPAeslint/prettier/vitest+vite build, then theui-taggedgo build/go test, then untaggedgolangci-lint 2.12.2— so the green light covers both the Go and web paths. Not re-run (it already vouches).Checks:
afk/96, body carries a workingCloses #96. ✅fix(spawn): …is Conventional Commits. ✅afk/96contains themaintip; no conflict.first_messageadded to the create request with shape-only validation mirroring theafk_promptprecedent (whitespace-only → none;afkPromptMaxBytescap → 400 verbatim otherwise), threadedinstanceCreateRequest → StartParams.FirstMessage → LaunchSpec.SeedPromptonto the trailing spawn positional.--settingsstill lands before the prompt;SeedPromptcarries no run-kind semantics. Covered byTestAPI_InstanceStartFirstMessage+TestStart_firstMessageIsTrailingSpawnPositional(zero keystroke batches asserted).req.first_messageis set beforestartInstance(correct ordering — no dependence on a post-spawnrun.id); empty box omits the key. ThequeuedMessagemodule, its transcript-availability gate, the auto-send effect, the pending bubble, and thedraftSeedplumbing are fully deleted with all consumers removed (eslint-green CI confirms no dangling refs).<Match>for the ready-to-start copy is correctly ordered ahead of the generic locating placeholder and is tightly guarded (!ended() && transcript()==="locating" && state()==="idle" && messages().length===0), so it cannot fire for a mid-conversation run or an ended run — verified by the rewrittenRunChat.test.tsxcases (state:""and ended run both keep the locating placeholder).Verdict: PASS. No blockers. Awaiting the human's free-text go-ahead to merge.