Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- Start a thread with a provider that gets the
t3-codeMCP server (seen with Claude). - Have the agent start a turn that asks the user a question (for example with
AskUserQuestion) and leave it unanswered for more than 24 hours. Keep the T3 server running the whole time. - Answer the question. The same turn continues.
- Have the agent call any
t3-codetool, for examplelink_pull_request.
Observed timeline: the question was asked at 2026-09-26 10:12 UTC, answered at 2026-09-28 06:19 UTC (44 hours later), and the link_pull_request call at 06:39 UTC failed. The server had been running without a restart since 2026-09-25.
Expected behavior
A provider session keeps a valid t3-code MCP credential for as long as the session is alive, however long a turn runs or waits on the user.
Actual behavior
Every t3-code tool call from that session is rejected:
MCP server "t3-code" rejected the Authorization header in its config (update it, then run /mcp to reconnect)
The session cannot recover. The provider process keeps the old bearer in its MCP config, so reconnecting with /mcp presents the same rejected token.
Cause, in apps/server/src/mcp/McpSessionRegistry.ts:
- Credentials live in memory, and any record whose
lastAliveAtis older thanDEFAULT_LIVENESS_WINDOW_MS(24 hours) is pruned. lastAliveAtis refreshed only by MCP traffic (resolve) and bytouchActiveMcpThread, whichProviderServicecalls fromsendTurnandcompactThread. Answering a pending user-input request or an approval does not refresh it, and nothing refreshes it while a turn is in progress.resolve,issueandtouchall prune before they refresh, so once 24 hours have passed a later touch cannot bring the record back.
Related, but separate:
- [Bug]: Preview MCP bearer token expires after 30 minutes idle and active Codex sessions cannot recover #4463 was the same symptom with a 30-minute window. Keep MCP credentials alive across provider turns #4659 fixed it by refreshing liveness on every provider turn. That fix covers sessions that go idle between turns; it does not cover a single turn that stays open longer than the window, which is the case here.
- preview_* tools silently disappear from running agents after a server restart #7870 covers credentials lost when the server restarts, because the registry is in memory. Here the server did not restart; the credential was pruned by the liveness window while the server kept running.
Impact
Major degradation or frequent failure
Version or commit
0.0.42; the same code is on main @ d15210c
Environment
Linux, Claude provider
Workaround
Let the turn finish, then restart the T3 server. The next message resumes the session with a fresh credential.
Activity
juliusmarminge commentedon Sep 28, 2026
Triage
Confirmed on
mainatd15210cd3d(same commit cited in the report). The diagnosis matches the code.The bearer is minted once in
prepareMcpSessionwhen the provider session starts (ProviderService.startSession, and session recovery only when no live process is adopted). It is not rotated on later turns.touchActiveMcpThreadruns only fromsendTurnandcompactThread.respondToUserInputandrespondToRequestdo not touch it, and nothing refreshes liveness while a turn is blocked.pruneDeadruns before the refresh inresolve,issue, andtouch. AfterDEFAULT_LIVENESS_WINDOW_MS(24h) with no MCP traffic and no newsendTurn/compactThread, the record is gone. A later answer cannot bring that bearer back, and the provider process keeps presenting it, which is why/mcpreconnect does not help. The 401 isinvalid_mcp_credential; the server warning isrejected MCP request with an unusable credentialwith reasonunknown_or_expired_token.This is the same gap #4659 left open. That change refreshes liveness at the start of each turn, so idle time between turns is covered. A single turn that sits on a user question or an approval for more than 24 hours is not. The same thing happens for any in-progress turn that goes 24 hours without a
t3-codecall. The comment onDEFAULT_LIVENESS_WINDOW_MSsays the window only bounds sessions that died withoutstopSession/stopAll. The implementation never checks that the session is still running.Touching on the answer is not enough for this report: the answer arrived at 44 hours, after the prune. Liveness has to be kept during the wait. The 24h bound should stay, because
/mcpis outside environment auth and this bearer is the only guard. A still-running session (in a turn, or blocked on user input or approval) should count as alive; a session that exits uncleanly should still expire. The existing registry test only covers touches that land inside the window.The restart workaround holds: recovery adopts a still-running process without issuing a new credential, so the next message only gets a fresh bearer after the provider process is gone. Stopping that session and sending a new message does the same thing without restarting the whole server. Sending another message while that process is still up does not.
fix(server): keep MCP credentials alive while provider sessions run
fix(server): keep MCP credentials alive while provider sessions run