Skip to content

preview_* tools silently disappear from running agents after a server restart #7870

Closed

Description

@thedouglenz

What happens

After the server restarts, agents in already-running threads lose their preview_* tools. The agent reports that the tools do not exist; nothing is logged and no error surfaces in the UI.

Why

McpSessionRegistry keeps issued credentials only in memory:

// apps/server/src/mcp/McpSessionRegistry.ts:98
const state = yield* SynchronizedRef.make<RegistryState>({ records: new Map() });

The acquireRelease finalizer only clears the module-level activeMcpSessionRegistry pointer, so the map simply dies with the process. A resumed provider session keeps presenting the bearer token it was issued before the restart, the auth middleware no longer has a record for that token hash, and every tool call is rejected.

This is reachable through ordinary use — anything that restarts the backend (an update, the desktop app restarting its server) while a thread has an active provider session.

Reproduce

  1. Start a thread with a provider that gets the t3-code MCP server attached.
  2. Restart the server.
  3. Ask the agent to use a preview_* tool.

The agent reports the tool does not exist rather than reporting an auth failure.

One thing worth knowing before fixing it

issueActiveMcpCredential revokes the thread's existing credentials before issuing new ones:

activeMcpSessionRegistry
  .revokeThread(request.threadId)
  .pipe(Effect.andThen(activeMcpSessionRegistry.issue(request)))

That matters if the fix is "re-issue on resume": a resumed session that is still using its old credential can have it revoked out from under it while the new config is in flight. Persisting credentials and reloading them on boot sidesteps that, but it needs the liveness writes to coalesce or every turn writes a row.

Context

Found downstream in a fork (thedouglenz/trellis), where the same registry serves an additional toolkit and the symptom was more obvious. Happy to leave this as a report — I know large PRs are not what you are looking for.

Activity

  1. satyalyadav commented on Oct 15, 2026

    @satyalyadav
    Contributor

    Observed from the other side of this on OpenCode server 2.0.29: after a service restart under heavy load, the restarted server logged an authentication failure while terminating an MCP session belonging to one of the thread-scoped t3-code-* servers. Log fields, with the connection UUID shortened:

    timestamp=2026-10-15T03:02:21.898Z message="mcp http authentication rejected" status=401 method=DELETE server=t3-code-4c2f11b8-…
    timestamp=2026-10-15T03:02:21.899Z message="failed to terminate MCP session" server=t3-code-4c2f11b8-… error="Failed to terminate session: Unauthorized"
    

    At 03:04:33.614Z the same named server logged mcp connected, tools=83, and the associated session ultimately reports outcome: succeeded.

    This establishes a cleanup-path authentication failure after a restart, consistent with the in-memory McpSessionRegistry analysis above, but I have not confirmed the disappearing-preview_* symptom or that the rejected token was absent from the restarted registry.

  2. juliusmarminge commented on Oct 27, 2026

    @juliusmarminge
    Member

    This no longer reproduces on current main (71ba6b3), so I'm closing it as fixed.

    The fix is de34391 (#2829, the new orchestrator). The revokeThread-then-issue path in issueActiveMcpCredential is no longer used. ProviderSessionManager.prepareMcpSession now resolves the thread's stored credential against the registry. After a restart the registry has no record of it, so the thread gets a fresh credential, and every adapter reads it when it starts its provider process. Neither the registry nor the old in-memory session config survives the restart, and no provider process does either, so nothing is left presenting a pre-restart token.

    What I ran, on a dev server with a fresh database:

    1. OpenCode, idle restart. An OpenCode thread (anthropic/claude-sonnet-5-5) called preview_status, and it returned normally. I stopped the server with SIGTERM (its opencode serve child exited with it) and started it again on the same database. The same thread called preview_status again: same result.
    2. Claude, mid-turn restart. A Claude thread called preview_status. Its next turn ran sleep 40 and then preview_status. I restarted the server during the sleep. After boot, the interrupted turn resumed and preview_status returned normally.
    3. Server trace. It shows one fresh McpSessionRegistry.issue per thread after each restart, and no /mcp authentication rejections.

    @satyalyadav, about the 401 on DELETE: that is the old provider session trying to end its MCP session with the old server's token while the server restarts. It fails without consequence, which matches the reconnect with 83 tools you saw two minutes later. If tools ever go missing after a restart on a current build, please open a new issue with the provider, the version, and the server log around the restart.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions