Conversation
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused server bug fix that prevents archive from detaching provider sessions during an active turn, while preserving queued and idle-thread behavior. The implementation is localized and backed by targeted orchestration and MCP tests. You can add or adjust custom eligibility rules. Learn more. |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 🧰 Additional context used📚 Code guidelines (1)No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (6)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughThread archiving now fails with ChangesThread archive active-turn guard
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Medium Suggested reviewers: Merge Risk: ⚪ Minimal · up to Archiving is refused while a turn is active and remains available afterward. No merge-blocking issue is identified; merge after normal checks. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change prevents archiving from disrupting an active turn, while preserving the inspected project and caller-ownership checks. No new material security issue was established. Confidence remains limited around cleanup failures and recovery after a successful archive. Retained concerns Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Fixes #15135.
Problem
thread.archiveonly refused a thread that was already archived. It cancelledqueuedruns, then detached every live provider session withrevokeMcpCredential: true, even while a run waspreparing,startingorrunning. An agent that archives its own thread witht3_thread_organize {action:"archive"}always hits this, because MCP mutations require the caller to own an active run. For a single-thread session like Claude, the detach closes the run's event stream, and the turn fails with the generic "The provider event stream closed unexpectedly". The archive itself succeeds. The clients already block archiving in this state (threadRuntimeCanArchive), but the server did not.Change
thread.archivenow fails with a new typedOrchestratorThreadTurnRunningErrorwhen any run of the thread ispreparing,startingorrunning. It is checked before any archive event or detach effect is planned, so a refused archive commits nothing.queuedruns are still cancelled by the archive, and awaitingrun (post-turn drain) still archives. The same guard applies whether the agent targets its own thread or another one, and it covers web and mobile dispatches too.OrchestratorDispatchErrorwith a text cause. A dedicated tag (likeOrchestratorSubagentThreadReadOnlyError) lets callers react to this rejection without parsing text. Its fixed message, "This thread cannot be archived while a turn is running. Archive it after the turn ends.", reaches web and mobile through the existing user-facing dispatch error mapping.t3_thread_organizemaps that tag toinvalid_requestwith "A thread cannot be archived while a turn is running. Archive it after the turn ends, from another thread or in the app." Every other dispatch error keeps the genericorchestration_error. The message points elsewhere because an agent cannot archive its own thread once its turn has ended.Orchestrator.tsassumed a guard that did not exist; it now names the real guards. The web thread-menu comment now says the server rejects archive while a run is preparing, starting or running, and that idle attached providers still archive.Scope and approval
This follows the triage comment: "make
thread.archivereject a thread with a preparing, starting, or running run, the way settle does. The agent would then get a clear error and the turn could finish, while queued work can still be cancelled." Deferring a self-archive until the turn ends was proposed and closed in #14328 (#14328), so this PR refuses instead of deferring.Open #14907 (#14907) relies on archiving a thread whose run is still
preparing. This guard follows the triage and the clients' existingthreadRuntimeCanArchiverule, which already blockpreparing. With it, #14907 would need to settle the preparing run first, or maintainers can decide which behavior wins.#15136 reports a related symptom from a planned detach on a workspace change. With this guard, archive no longer detaches a provider mid-turn. The workspace-change detach is a separate path and is not changed here.
Server only, plus one web comment. No contract change.
Verification
Observed result. Real dev servers at 18b2132, before and after the fix, driven through the authenticated MCP HTTP endpoint with the thread's own credential from the real Claude adapter. A deterministic Claude CLI stand-in holds the provider turn open until released; no live model call. The capture used the
starting/runningguard, beforepreparingwas added; that status is covered by the tests below.t3_thread_organize {action:"archive"}during the turn{sequence}; archives the thread; the run fails 8 ms later with "The provider event stream closed unexpectedly…"invalid_requestwith the message above; the thread stays unarchived and the run keeps runningBefore, archived and failed:

After, archive refused and the turn still running:

After, the turn completed:

After, archived in the app:

Tests. 148 pass across 11 server suites: 63 in
runtimeLayer.test.tsandOrchestratorMcpToolkit.integration.test.ts, plus 85 in the nine other suites that dispatchthread.archive. The tests cover:preparing,startingandrunning, with no events, no outbox effects, an unchanged event sequence andarchivedAtstill null; the same thread archives once its run completes;waitingrun still archives, and queued work is still cancelled;invalid_requestwith the provider session still attached;Orchestrator.control-reads.test.tsnow ends its deferred (preparing) run before archiving. Negative control: with the guard disabled, the three status cases and the MCP integration test fail (4 failed, 62 passed across those three files). Server typecheck has no errors or warnings. Targeted lint passes, apart from one existinglayerUnavailablewarning. Format and both knip phases pass.Not checked: a live Claude model and its tool selection, native desktop and mobile clients, other real providers, relay and tunnel modes, project deletion or scheduled tasks at runtime, a deliberately delayed overlap between checkpoint capture and detach, background-subagent termination, and the separate preparing-run race in #14907.
Implemented with Claude Code (Claude Opus 5.5, coordinated by Claude Fable 5.1); tests, independent review and the observed result by GPT-6 Astra via Codex.