Skip to content

fix(server): Pi rollback works past a turn you stopped - #13797

Merged
juliusmarminge merged 1 commit into
t3code/codex-turn-mappingfrom
fix/pi-rollback-after-stop
Sep 26, 2026
Merged

juliusmarminge merged 1 commit into
t3code/codex-turn-mappingfrom
fix/pi-rollback-after-stop

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Rolling back to a Pi checkpoint fails when any later turn was stopped. Found live with Pi 0.87.1 and openrouter/deepseek/deepseek-v4-flash: turn 1, turn 2 stopped mid-tool, turn 3, then roll back to turn 1. No run was marked rolled back, Pi still had every message, and the effect worker retried the rollback until it gave up:

WARN Orchestration effect execution failed { effectType: 'provider-thread.rollback', attemptCount: 1..3,
  CheckpointRollbackExecutionError → ProviderAdapterRollbackThreadError →
  ProviderAdapterProtocolError: pi provider protocol error: Pi rollback target has no captured session-tree entry. }

The web client's rewind waits for a rolled_back run, so it times out after 120s.

Cause

Stop always sends requestRuntimeRestart, so PiAdapterV2.interruptTurn aborts and then terminates Pi. Pi settles the aborted turn (agent_settled), but finalizeTurn's get_entries then hits a dead transport, so the stopped turn keeps its weak synthetic nativeTurnRef. piRollbackForkEntry forks at the first discarded turn's user entry, gets no strong ref, and fails. Pi does record that user entry, so the session file has it. We just never read it.

Fix

In the Stop-with-restart branch, read the turn's session-tree refs after the abort and before terminating, bounded by the same 2s as the abort. finalizeTurn reuses them instead of querying the dead process. The read runs under the session event permit, so it cannot race a finalize that is already reading the tree.

Verification

  • New replay fixture thread_rollback_after_stop (Pi), recorded live with scripts/record-pi-rpc-replay-fixture.ts. It runs turn 1, stops turn 2 mid-tool, runs turn 3, rolls back to turn 1, then sends a prompt. It asserts that the stopped turn's ref is strong, that the get_entries read comes after the abort, that fork targets that entry, and that the final answer only knows turn 1.
    • With the adapter change reverted, it fails: the rollback never lands and the scenario times out at await_thread_idle.
    • With the change, it passes.
  • Re-recorded turn_interrupt_mid_tool/pi_transcript.ndjson, which now has the extra get_entries before the process exits, and added an assertion that its interrupted turn carries a strong ref.
  • vp test run src/orchestration-v2/testkit/OrchestratorReplayFixtures.integration.test.ts -t "/pi through": 8/8 passed.
  • vp test run src/orchestration-v2/Adapters/PiAdapterV2.test.ts: 49/49 passed.
  • vp exec tsc --noEmit -p . (apps/server): clean. Knip on apps/server exports: clean. vp lint on touched files: only the pre-existing unused layer warning.
  • Not run: repo-wide checks, the other providers' fixtures.

OpenCode doesn't have this gap. Its Stop only calls session.abort and keeps the server alive, and a live rollback past a stopped OpenCode turn worked.

Model: Claude Opus 5.5 (Claude Code)

🤖 Generated with Claude Code


Devin Review

Stop always restarts the Pi runtime, so the adapter killed Pi before
finalizeTurn could read the session tree. The stopped turn kept its weak
synthetic ref, and any later rollback past it failed with "Pi rollback
target has no captured session-tree entry" while the effect retried.

Read the turn's session-tree refs after the abort and before terminating,
and let finalizeTurn reuse them.

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:XL 500-999 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: 47af1fa · 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 47af1fa

Macroscope's review found this PR approvable — This is a narrowly scoped Pi rollback bug fix that captures session-tree references before Stop terminates the provider and adds targeted replay coverage for the affected path. The remaining changes are test fixtures and generated transcript data, with no product-default or static-analysis override changes.

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

@juliusmarminge
juliusmarminge merged commit d2269c3 into t3code/codex-turn-mapping Sep 26, 2026
24 of 25 checks passed
@juliusmarminge
juliusmarminge deleted the fix/pi-rollback-after-stop branch September 26, 2026 18:30
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:XL 500-999 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