fix(server): recover from provider interrupt failures - #7412
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — ... You can add or adjust custom eligibility rules. Learn more. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 94c2cb0. Configure here.
## What's Changed * fix(server): bound OpenCode skill discovery output by @Lucenx9 in pingdotgg/t3code#7675 * fix(mobile): persist thread shelf collapse state by @PixPMusic in pingdotgg/t3code#5152 * fix(mobile): restore Android tablet thread controls, clean up header by @PixPMusic in pingdotgg/t3code#5385 * fix(mobile): land the first thread open above the composer on Android by @PollyGlot in pingdotgg/t3code#5585 * fix(server): check out submodules in a new worktree by @Brechard in pingdotgg/t3code#7674 * fix(server): preserve merged PR badges after branch deletion by @tris203 in pingdotgg/t3code#6216 * fix(server): return fresh live pull request reads by @Adamulek123 in pingdotgg/t3code#6472 * fix(web): compare client and server versions as semver, not strings by @spiky02plateau in pingdotgg/t3code#7579 * fix(web): stop follow-ups from leaving giant blank space by @t3dotgg in pingdotgg/t3code#8068 * fix(marketing): stop automatic Vercel deployments on pull requests by @t3dotgg in pingdotgg/t3code#8070 * chore: vouch repeat contributors by @t3dotgg in pingdotgg/t3code#8071 * fix(server): keep the authoritative subagent model when snapshots race task_started by @spiky02plateau in pingdotgg/t3code#7583 * fix(server): honor auto-accept edits for the OpenCode provider by @Rishet11 in pingdotgg/t3code#7100 * fix(server): run the CLI on Node versions without import.meta.main by @CDVolvik in pingdotgg/t3code#7141 * fix(server): recover from provider interrupt failures by @mrmg in pingdotgg/t3code#7412 * fix(server): recreate a thread's worktree before starting a turn by @mackinleysmith in pingdotgg/t3code#7839 * fix(server): thread delete no longer fails on already-removed worktrees by @t3dotgg in pingdotgg/t3code#8076 * fix(web): stop update notices showing through the composer by @t3dotgg in pingdotgg/t3code#8083 ## New Contributors * @spiky02plateau made their first contribution in pingdotgg/t3code#7579 **Full Changelog**: pingdotgg/t3code@v0.0.34-nightly.20260824.1175...v0.0.34-nightly.20260824.1176 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.34-nightly.20260824.1176
Take main's provider interrupt recovery (pingdotgg#7412): interruptTurn failure stops the session and marks it stopped. Drop OpenCode zombie settle-to-ready (9de48da), which is not Codex/Claude teleport. Keep the HTTP native-revision gate plus attachment cleanup, and the ChatComposer teleport lock ahead of attachment-upload disable. Co-authored-by: aanishbhirud <aanishbhirud@gmail.com>
Range 2274444..f035a0f. 80 files touched by both sides; 20 conflicted, 33 hunks. Four of upstream's commits landed in subsystems this fork has rewritten, and three of the conflicts were git splicing two unrelated blocks onto shared scaffolding. Competing implementations, resolved toward a hybrid superset: - pingdotgg#5891 (stop kills lingering Claude work) rewrote `interruptTurn` into a single hard `stopSessionInternal`. This fork's Stop is a two-rung client ladder (cooperative interrupt, then a deliberate second press escalating to `session.stop`), so collapsing the rungs would make that band vestigial for Claude and charge every stop-to-redirect a cold restart. Kept the fork's cooperative `interruptTurn` (bounded `stopTask` sweep + bounded `query.interrupt()`); took upstream's hardened `stopSessionInternal` (`close()` at the very top, `task.completed` sweep over `liveTaskIds`, `stopSessions` failure collection) and kept the fork's bounded `Fiber.interrupt` on top. Upstream removed `ClaudeQueryRuntime.interrupt`/`.stopTask` OUTSIDE any conflict marker; both restored. Upstream's four new tests retargeted to `stopSession`, not deleted. - pingdotgg#7412 (recover from provider interrupt failures) adopted on top of the fork's live-session gate, which settles to `stopped` rather than erroring when nothing is live. Its three tests only projected a session, so each needed a `runtimeSessions.push` to reach the path it tests. - pingdotgg#7969 (settled pinned threads) extracted the pin markup into `pinIndicator` byte-identically; took the extraction, kept `ThreadProviderChip`. - pingdotgg#7949 (Codex feedback) added a send-abort condition; folded into the fork's `shouldAbortSendBeforeOfflineQueue` helper so every abort reason stays in one tested place, with a case for it. - pingdotgg#8048 (attachment uploads) and pingdotgg#7999/pingdotgg#7971 merged additively; the offline-outbox branch stays ahead of the new feedback branch in `handleSend`, since it must run before anything mutates the draft. Three conflicts were splices, resolved by reset-and-reapply rather than hunk surgery: `ClaudeAdapter.test.ts` and `ProviderCommandReactor.test.ts` (two unrelated tests sharing `makeHarness()` scaffolding) and `http.ts` (two route layers sharing an `Effect.gen` preamble; `attachmentUploadRouteLayer` re-added whole). Invariants: all ten re-probed against the merged tree and holding. Migrations 47 entries, ids unique and monotonic, max 48, 34 still burned; patches now 16 files with exactly one fork-owned and 15 byte-identical to upstream (upstream added `@legendapp/list`); `Sidebar.tsx` still the default; the three MessagesTimeline helpers, the Collapsible wrapper, the steering test and `serverRuntimeStartup.reconcile.test.ts` all still absent; four `isSendBlocked=` sites; both footer panels inside `SidebarUtilityMenu`; both project-row entry points. Registry gains invariants 7-9 for the decisions above. Sweeps: resurrected 1, dropped 11, fork-loss 17, every entry named. The one resurrection is a text-level false positive (a bare `if (` the fork restructured, matching upstream's new attachment block verbatim). Two real findings the sweep caught that the gate could not: the fork's deliberate deletion of `shouldShowBranchMismatchBanner` was re-imported in two files by an additive resolution, and `ChatMarkdown.tsx` carried a `markdown-file-link-tooltip-scroll` class that was never defined in any stylesheet — a pre-existing fork defect, so the tooltip's thin scrollbar had silently fallen back to the browser default. Restored upstream's working inline utilities. Gate (`pnpm verify`, the fork's only gate — CI never runs here): format clean, typecheck 0 errors, lint 23 warnings and 0 errors (all pre-existing), tests 9873 passed / 7 skipped / 0 failed across 14 projects. Three failures were fixed en route: the third pingdotgg#7412 test above, an invalid `status: "starting"` in the runtime-session fixture it needed, and upstream's `entrypoint.test.ts` symlink case, which cannot pass on macOS because `os.tmpdir()` is itself a symlink that `realpathSync` resolves along with the fixture's own link — proven with a live probe, fixed in the fixture only, and worth sending upstream. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Fixes #7368
Problem
If a provider rejects an interrupt request, the projected session remains running and leaves the thread stuck in a working state.
Fix
Stop the provider session best-effort, then project the session as stopped, clear its active turn, and record the provider failure. Cancellation-only stop failures remain cancellations.
Verification
vp test run apps/server/src/orchestration/Layers/ProviderCommandReactor.test.tsvp run --filter t3 typecheckvp fmt --check apps/server/src/orchestration/Layers/ProviderCommandReactor.ts apps/server/src/orchestration/Layers/ProviderCommandReactor.test.tsCreated with GPT-5.6-terra in T3 Code.
Note
Fix
ProviderCommandReactorto recover from provider interrupt failuresWhen
providerService.interruptTurnfails during athread.turn-interrupt-requestedevent, the reactor now stops the session, clearsactiveTurnId, records the failure detail inlastError, and emits aprovider.turn.interrupt.failedactivity.stopSessionitself fails during recovery, a warning is logged but the failure is otherwise swallowed (except for pure interrupts).Macroscope summarized 7469c37.
Note
Medium Risk
Changes orchestration session lifecycle on interrupt failures in a critical reactor path, though recovery is guarded against overwriting ready/stopped state and is covered by new tests.
Overview
When a user interrupts a turn but
providerService.interruptTurnfails, threads no longer stay stuck in a running/working session state.processTurnInterruptRequestednow catches interrupt failures and runs recovery: it re-reads the latest projected session, skips work if the session already becameready,stopped, or the active turn changed, otherwise best-effortstopSession, then projects the session asstoppedwithactiveTurnIdcleared,lastErrorset from the provider error, and aprovider.turn.interrupt.failedactivity. Cancellation-only failures on stop are logged and do not block that projection.Tests add injectable
interruptTurnEffect/stopSessionEffecthooks and cover running andstartingsessions, plus a race where natural completion toreadymust not be overwritten.Reviewed by Cursor Bugbot for commit 7469c37. Bugbot is set up for automated code reviews on this repo. Configure here.