Skip to content

fix(server): Codex rollback works after the app-server restarts - #13787

Merged
juliusmarminge merged 2 commits into
t3code/codex-turn-mappingfrom
fix/codex-rollback-resume
Sep 26, 2026
Merged

juliusmarminge merged 2 commits into
t3code/codex-turn-mappingfrom
fix/codex-rollback-resume

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

After a server restart (or the 30-minute idle release of the Codex session), "Edit from here" on a Codex thread never finishes. The rollback effect fails five times with thread not found: <native id> from thread/revert, and the web client sits on "Thinking" until its 120 s timeout.

Seen live on Codex 0.156.1 / gpt-6-luna: I ran two turns, restarted the server, then used Edit from here on the first message. The provider log shows the fresh app-server answering thread/read with "status":{"type":"notLoaded"}, thread/turns/list succeeding, and thread/revert returning -32600 thread not found. That repeated for all 5 effect attempts, and the outbox row ended failed.

Why

In Codex 0.156, thread/revert goes through load_thread, which only returns threads loaded in the current app-server process (thread_processor.rs, thread_revert_response → load_thread → thread_manager.get_thread). thread/read and thread/turns/list read the stored rollout, so they work on an unloaded thread, and the adapter got all the way to the revert. The rollback service opens a fresh session but never resumes the thread. Resume normally happens only in turn start.

Fix

The Codex adapter's rollbackThread already calls thread/read to check the history mode. It now also reads thread.status, and when that is notLoaded it calls thread/resume (with the same runtime params a turn start would send) before thread/revert. Threads that are already loaded skip the extra request, so the existing thread_rollback transcript is unchanged.

Proof

  • New replay fixture thread_rollback_after_restart, recorded live from Codex 0.156.1 with gpt-6-luna and not hand-edited. The recording has two turns, an app-server restart, thread/read (notLoaded), thread/resume, thread/turns/list, thread/revert, and a post-rollback turn. The fixture input advances the clock 31 minutes so the orchestrator releases the session between the second turn and the rollback. The assertions reuse the thread_rollback checks: run 2 ends rolled_back, and the post-rollback answer contains only the first exchange. They also check that the resume comes right before listing and reverting.
  • Before the fix, the fixture fails: the adapter sends thread/turns/list where the transcript expects thread/resume, so the rollback never completes and the next run never leaves starting. After the fix it passes.
  • The recorder gains the thread_rollback_after_restart scenario, so the fixture can be re-recorded.

Verification

  • vp test run src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t "thread_rollback|thread_fork": 4 passed (thread_rollback for Codex, Claude, and Pi, plus the new fixture).
  • vp test run src/orchestration-v2/testkit/ThreadFork.integration.test.ts: 6 passed. The fork path shares the thread/read helper.
  • vp test run src/orchestration-v2/testkit/CodexReplayFixtures.integration.test.ts src/orchestration-v2/testkit/OrchestratorReplayFixtures.contract.test.ts: 4 passed.
  • vp test run src/orchestration-v2/Adapters/CodexAdapterV2.test.ts -t "rollback|revert|Rollback": 5 passed.
  • vp exec tsc --noEmit -p . in apps/server: no errors or warnings. vp lint/vp fmt on the touched files show only the two no-unused-vars warnings that already exist in CodexAdapterV2.ts. vp run knip:check is clean.
  • Recorded with T3_CODEX_BIN=<codex 0.156.1> node scripts/record-codex-app-server-replay-fixture.ts --scenario thread_rollback_after_restart.
  • Not run: the full server suite.

Model: Claude Opus 5.5 (Claude Code)

🤖 Generated with Claude Code


Devin Review

Codex 0.156 only runs thread/revert on a thread loaded in the current
app-server process. After a server restart or an idle session release,
the rollback reached a fresh process, thread/revert failed with "thread
not found", and Edit from here hung until the client timed out. The
adapter now resumes an unloaded thread (thread/read reports notLoaded)
before reverting.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ No successful main baseline artifact is available yet. This run establishes the initial measurement.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire — 4.9 KiB — 6.8 KiB ✅
Codex Thread snapshot wire — 3.7 KiB — 4.9 KiB ✅
Codex Live turn WebSocket wire — 1.1 KiB — 2.0 KiB ✅
Codex Live turn WebSocket decoded — 20.4 KiB — 29.3 KiB ✅
Codex Live turn messages — 1 — 8 ✅
Claude Total thread wire — 4.9 KiB — 6.8 KiB ✅
Claude Thread snapshot wire — 3.7 KiB — 4.9 KiB ✅
Claude Live turn WebSocket wire — 1.2 KiB — 2.0 KiB ✅
Claude Live turn WebSocket decoded — 20.8 KiB — 29.3 KiB ✅
Claude Live turn messages — 2 — 8 ✅

Baseline: unavailable · PR result: f6d73ce · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 106.1 KiB
  • Claude decoded thread snapshot: 106.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at f6d73ce

Macroscope's review found this PR approvable — This is a narrowly scoped Codex rollback bug fix: unloaded threads are conditionally resumed before thread/revert, while loaded-thread behavior remains unchanged. The added replay fixture and assertions cover the restart/idle-release path, and the other changes are test or fixture infrastructure.

No code changes detected at ab3de67. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit 2c388da into t3code/codex-turn-mapping Sep 26, 2026
22 checks passed
@juliusmarminge
juliusmarminge deleted the fix/codex-rollback-resume branch September 26, 2026 18:38
juliusmarminge added a commit that referenced this pull request Sep 26, 2026
Brings in the V2 bug-hunt fixes merged while this PR was refreshed
(#13787, #13789, #13790, #13797, #13805, #13806). No conflicts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant