Skip to content

Agent archiving its own thread with t3_thread_organize fails the running turn with "provider event stream closed unexpectedly" #15135

Description

@0pilatos0

What happened

When an agent calls t3_thread_organize with action: "archive" and no threadId (its own thread) during its turn, the archive succeeds. The run then ends as failed with "The provider event stream closed unexpectedly", even though every earlier step in that turn succeeded. Expected: either the archive waits until the turn ends, or self-archive is refused with a clear error. Reproduces 4 out of 4 runs, desktop app, macOS arm64.

Diagnosis

thread.archive has no active-run guard, and it tears down the caller's live provider session.

  1. t3_thread_organize (apps/server/src/mcp/toolkits/thread/handlers.ts:275) dispatches thread.archive for the caller's thread. assertLiveCaller (mcp/threadAccess.ts:40) requires the caller to have an active run, so a self-archive always targets a thread in the middle of a turn.
  2. The decider only rejects threads that are already archived (orchestration-v2/Orchestrator.ts:2264). Archive cancels queued runs only (:2964) and leaves the running run alone. It then detaches every provider session with detail: "Thread archived." and revokeMcpCredential: true (:3033-3106). The comment at :3026 says this is safe for settle because settle rejects active runs. Archive has no such check.
  3. For a single-thread session (Claude here), ProviderSessionManager.detach releases the entry (:1871). releaseEntry then calls failSubscribers (:512), which pushes ProviderAdapterEventStreamError("Thread archived.") into the run's event stream.
  4. RunExecutionService (:1267-1307) still sees the run as running, so it writes a failed terminal. ProviderFailure.ts:47 maps that to the generic "stream closed unexpectedly" text, and "Thread archived." is never shown.

For comparison: settle rejects active runs (Orchestrator.ts:2352), delete cancels them before detaching (ThreadDeletion.ts:70), and the web UI disables Archive while a thread is running (threadActionMenu.logic.ts:51). That UI code's comment says the server rejects the action, which v2 no longer does. Codex and OpenCode2 sessions take the interruptTurn path in detach, so they would likely end as interrupted instead (not tested). On main (e8545b2), Orchestrator.ts and the MCP toolkit are unchanged since this tag, and releaseEntry still fails subscribers.

Possible fixes: reject thread.archive while a run is active, as settle does, so the agent gets a clear error. Or defer a self-archive until the turn ends.

Steps to reproduce

  1. In the desktop app, start a thread on Claude.
  2. Ask the agent to call t3_thread_organize with action: "archive" and no threadId, then reply "done".
  3. The thread is archived within about 10 ms of the tool call starting, and the run fails with "The provider event stream closed unexpectedly". The tool call item stays running.

Version

0.0.46-nightly.20261003.2623 (fed41fa)

Environment

macOS 27.0 (26A428) arm64, Node v26.8.2, Claude Code 2.1.288, desktop app with local server

Evidence

# statev2.sqlite projections, 2026-10-03 (UTC). These are the only failed runs in the database.
thread    tool started  archived_at   run failed    last tool call
cd17f2c6  09:29:55.154  09:29:55.165  09:29:55.172  t3_thread_organize {"action":"archive"} [running]
feb9e7ee  09:35:33.715  09:35:33.722  09:35:33.729  same
5f2febba  09:55:35.674  09:55:35.683  09:55:35.689  same
3d61eeab  09:55:36.278  09:55:36.289  09:55:36.294  same
94e4b217  09:58:11.197  09:58:11.200  09:58:11.204  same
67d809c6  09:58:17.022  09:58:17.026  09:58:17.029  same

error turn item: {"class":"unknown","message":"The provider event stream closed unexpectedly. Retry the turn; if it keeps failing, check the provider and server logs.","code":null,"retryable":null}

Related issues

#14203 (closed) asked for archive to stop the provider session, which v2 now does, but nothing handles a running turn. #14907 (open PR) covers archived threads starting work after setup, which is a different path. I found no existing issue for archiving a thread with a running turn.

Fix applied or workaround

Nothing changed on the machine. Workaround: archive from another thread, or manually after the turn ends. Settle is not a workaround from inside the turn, because it rejects active runs.

Filed by

Claude Code 2.1.288 (claude-opus-5-5) via t3 triage

Activity

  1. juliusmarminge commented on Oct 3, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks @0pilatos0 for the detailed diagnosis and the database evidence! I confirmed this on current main (263097a8). The nightly you reported on (fed41fa8) is an ancestor of that commit, and nothing since has changed this path.

    What happens

    thread.archive only refuses a thread that's already archived. It cancels queued runs, then detaches every live provider session with detail Thread archived. and revokeMcpCredential: true. A run that is preparing, starting, or running is left in place. By comparison, settle rejects active work before it detaches, and delete cancels active runs first. Archive does neither.

    The clients already treat archiving a live turn as off-limits. threadRuntimeCanArchive allows dropping queued work but not detaching a provider that's preparing, starting, or running, and the web and mobile archive actions are disabled in that state. The menu comment saying the server rejects the action is out of date.

    A self-archive through t3_thread_organize (with action: "archive" and no threadId) always hits this. readWritableThread requires the caller to own an active run, then dispatches thread.archive on that same thread. For a single-thread session like Claude, detach releases the session, and failSubscribers pushes a ProviderAdapterEventStreamError with cause Thread archived.. RunExecutionService still sees the run as running, so it writes a failed terminal. ProviderFailure then swaps the cause for the generic "provider event stream closed unexpectedly" text, which matches all six rows you posted. The tool item stays running because the stream dies before the tool result comes back.

    Shared multi-thread sessions (Codex, OpenCode) interrupt or unload rather than going through releaseEntry, so they may end up interrupted instead of failed. That's still untested.

    Related

    #14203 (closed) was about stopping an idle provider process on archive. That teardown is now what kills the live turn. Open PR #14907 is a different path: setup finishing and starting work after the thread is already archived.

    Likely fix area

    One option is to make thread.archive reject 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 would be a new workflow. A maintainer will decide on the fix direction.

    Your workaround holds: archive the thread only after its turn is idle, either from the UI or from another thread. Archiving it from anywhere while the turn is still running goes through the same server path.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions