[Bug]: Send turns grey ("This connection cannot change threads") when the session permission check times out under database load#24221

opened6d agobyMmaxibotstef

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Different from #7390 and #17422 (mobile draft state) and #14428 (active turn not cleared). Related to the database contention in #23000 and #23687.

Area

apps/web

Steps to reproduce

  1. Use a large state database where some server reads take several seconds (see #23000), or, for a deterministic repro, add a delay of more than 6 s to AuthSessionRepository.getById on the server.
  2. Open a thread in the desktop app and type a message.
  3. Wait for the session state to revalidate (it is stale after 30 s) while the server is slow.

Expected behavior

A slow or failed session check keeps the last known permissions, or retries, and Send stays enabled. If Send is disabled, the reason says the server is busy.

Actual behavior

Send turns grey with the reason "This connection cannot change threads." It stays grey until a later session check finishes in under 6 s. The draft and the thread history are kept, but nothing can be sent.

Cause, from reading the code on main:

  • ChatView.tsx disables Send when useEnvironmentScope(environmentId, AuthOrchestrationOperateScope) is false.
  • apps/web/src/state/session.ts sessionHasScope returns false whenever the session result is a Failure, even if an earlier check succeeded.
  • packages/client-runtime/src/state/session.ts fetches /api/auth/session with a 6 s timeout (DEFAULT_SESSION_STATE_TIMEOUT_MS) and revalidates every 30 s (Atom.swr).
  • On the server, that request reads the auth session row from SQLite (SessionStore.verify → getById), so it waits behind slow database work.

So one slow permission read turns a still-valid session into "no permission".

Impact

Major degradation or frequent failure

Version or commit

T3 Code (Nightly) 0.1.3-nightly.20261023.3579

Environment

macOS 26.6.2, Apple Silicon, desktop app, large V2 state database. Before a local patch: 189 database operations of 5 s or more in 1.6 hours (longest 33 s).

Logs or stack traces

No response

Screenshots, recordings, or supporting files

No response

Workaround

A local build that moves background thread listings off the main database reader and narrows the slow listing queries made the stalls rare, and Send came back. The client-side behaviour above is unchanged by that patch, so a long enough stall would still grey out Send.

Synced from GitHub

Note

Posted by Claude Code (Opus 5.5) on behalf of the account owner.

Same symptom on Linux desktop 0.1.3-nightly.20261023.3592: Send stayed grey with "This connection cannot change threads", switching threads didn't help, a second remote environment on the same client worked normally, and View > Reload (Ctrl+R) fixed it. The stored desktop session still had orchestration:operate; Settings > Connections showed the local environment as "Permissions not checked".

At the time the server was under database load: an authenticated /api/auth/session request took over 30 s (the client gives it 6 s), and server traces showed reads with db.lock_wait_ms of 54–56 s while the queries themselves took milliseconds. Over 2 minutes, sqlite spans spent 683 s in lock wait (18,606 statements). So the failed permission lookup sticks in the client until a reload, rather than being retried once the server answers again.

Synced from GitHub

Independent confirmation on Windows 11 Pro x64 build 26300, official 0.1.3-nightly.20261024.3617 (27e0c684c585f955252d65f06206dd11a143bff5), with a 53.98 GB V2 database.

The user sees “Permissions not checked” and “This connection cannot change threads,” with sending/other mutating controls unavailable. Reload restored controls once, then failed to help during a later episode. That episode recovered during read-only diagnostics, without a restart or repair.

In the 2026-10-24 diagnostic window, session-row SELECT spans reached 16,179 ms (maximum recorded lock wait 16,178.771 ms). A separate reader transaction spent 30,182.046 of 30,186.8685 ms acquiring the reader. The installed release explicitly routes auth-session getById through NodeSqliteClient.readOnly, alongside recovery/history work. This supports the shared-reader contention path behind the six-second session check; it does not isolate a single cause for every wait.

I consolidated the full backend evidence on the existing query-performance issue: Windows trace excerpts, release-pinned source links, before/after query plans, fixture limits, exact ordered-result checks, all repeated timings and a tested Node.js reproducer.

Three indexes reduced recovery-query cost by about 5× in the freshly rerun public reproducer (earlier harness runs: 6.6–8.9×). These are isolated SQL measurements, not a proven live UI repair. The production maintenance window is deferred, and no indexes or app patch have been applied to the live installation.

Investigated and posted with GPT-6 Astra through Codex at the affected user's request. No private database, transcripts, credentials or unredacted logs are published.

Synced from GitHub

Note

Posted by Claude Code (Opus 5.5) on behalf of @omar7550.

Same on macOS desktop 0.1.3-nightly.20261024.3630 (23 GB V2 database). Two details that may help when checking #24445:

  • No retry after the timeout. The client trace shows a successful fetchEnvironmentSessionState at 19:52:11 UTC. A 3.1 s event-loop stall follows, then a failure at 19:52:21 ("/api/auth/session timed out after 6000ms"). After that there was no session check at all for about 6.5 minutes, so it did not recover once the 30 s staleTime passed. View → Reload at 19:58:46 (same renderer process, no server restart) ran a check that succeeded at 19:58:55 and restored access. That fits revalidateOnMount: true in packages/client-runtime/src/state/session.ts:145.
  • More than Send was disabled. "Open in Finder", "Choose editor" and "Initialize Git" were disabled, and the snooze actions disappeared from thread rows, while threads kept streaming. These are also gated on orchestration:operate through useEnvironmentScope, so they should be covered by the shared rule in #24445.

Screenshot of the disabled composer attached.

Image
Synced from GitHub

Note

Posted by Claude Code (Opus 5.5) on behalf of the account owner.

Same symptom on Windows 11 Home x64 (10.0.26200), desktop app T3 Code (Nightly) 0.1.3-nightly.20261025.3692, local server on 127.0.0.1:3773, ~17 GB V2 state database.

  • Send was greyed out with "This connection cannot change threads." in every thread on the local environment, including a brand-new thread with no project. Threads were still streaming live, so messages sent from the Android app showed up on the desktop right away.
  • The Android app, paired to the same machine, could still send normally the whole time.
  • View → Reload (Ctrl+R) fixed it right away, with no server restart.
  • In the ~25 minutes of server traces still on disk, db.lock_wait_ms reached 5,318 ms, with several other waits of 2–3 s. That's close to the client's 6 s /api/auth/session timeout. The span from the original failure had already rotated out of the logs, so I can't show the timed-out request itself.

This matches the cause described above, and it would be covered by #24445.

Triaged with Claude Code (Opus 5.5) via t3 triage.

Synced from GitHub

Additional corroboration on macOS Apple Silicon, official T3 Code Nightly 0.1.3-nightly.20261026.3740, packaged commit 6838e31b9c29, with a local V2 database of approximately 30 GB.

The user reported that actions were unavailable across their threads. We directly observed the desktop composer disabled with “This connection cannot change threads.” Existing history and live activity were still visible.

Retained trace evidence, October 26 PDT / October 27 UTC:

  • At 2026-10-27 00:40:35.331 UTC (October 26, 5:40:35 PM PDT), the renderer's local GET /api/auth/session request lasted 6,462.5 ms and recorded effect.fiber.interrupted: true.
  • The corresponding server-side request began at 00:40:35.353 UTC, lasted 6,541.770 ms, and ended HTTP 499 / Interrupted.
  • Concurrent SQLite reader transactions lasted approximately 11.9–12.0 seconds, including 11,806–11,889 ms acquiring the reader. Another transaction lasted 11,213 ms. These observations support the shared-reader contention explanation; we did not isolate the underlying expensive query.
  • No further local-loopback session GET appeared in the retained traces between that interrupted check and the window reload at 01:03:52 UTC (6:03:52 PM PDT). Other connections to the same server did complete session checks during the interval; their successful requests did not restore this desktop window's controls.

Verified recovery: View → Reload (⌘R) restored the permission-gated controls without restarting the backend. After reload, the local session request returned HTTP 200 in approximately 3.19 seconds. We entered an unsent test draft and directly verified that Submit message became enabled, then cleared the draft without sending. Snooze controls also returned. The backend process remained running throughout.

Installed-code confirmation: the packaged session-*.js still rejects scopes whenever result._tag === "Failure", matching the source at the packaged commit. The shared session code at that commit retains the 6,000 ms timeout and Atom.swr({ staleTime: 30_000, revalidateOnMount: true }).

This corroborates that the failure remains present in the October 26 Nightly and that a renderer reload can recover it. It does not establish that this release introduced the bug, or that reload is a durable fix. #24445 was still open and unmerged when checked.

Posted with the account owner's explicit authorization after local investigation through Codex. Private thread/project identifiers, network addresses, credentials, and raw logs are omitted.

This is fixed on main by 024e593a63 (#25526, "a slow host no longer shows settings as read-only"). A session check that times out is now retried with a doubled timeout (6 s → 12 s → 24 s → 48 s → 60 s), and the last successful session stays in place while the retry runs. The 6 s timeout alone no longer turns Send grey.

What I ran, in the web dev server on a fresh database at main 8f0b729faf, with the reporter's deterministic step (a 7 s delay added to AuthSessionRepository.getById), and a session recheck triggered by a server reconnect:

  • Control, the client from before the fix (024e593a63^ session.ts): /api/auth/session is cut off after 6.0 s with no retry. Send goes disabled with "This connection cannot change threads." and stays that way. before
  • main: the first check is cut off after 6.0 s, and the retry completes after 7.0 s with authenticated=true. Send stays enabled throughout, and the draft is kept. after
  • main, 20 s stall: the WebSocket connection itself also authenticates through getById and stops answering. Send shows "Environment disconnected" with a "reconnecting, did not answer" banner instead of the permission message, and it re-enables once the stall clears.

Server timeline: server-session-timeline.log. Delay patch used: fault-injection-24221.patch. packages/client-runtime/src/state/environmentHttpAuth.test.ts (which covers the retry) passes on main.

If Send still greys out with this message on a build that contains 024e593a63, please reopen with the version and how long the stall was.