Skip to content

[Bug]: Pending user-input popup can never be dismissed after the provider session dies (e.g. server restart) #5454

Closed

Description

@NeilTheFisher

Before submitting

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

Area

apps/web + apps/server

Summary

If the provider session for a thread dies while a request_user_input / AskUserQuestion popup is pending — e.g. the user restarts the server, or the provider process exits — the popup becomes permanently stuck:

  • The message can't be submitted. Submitting appends a provider.user-input.respond.failed activity whose detail is "No active provider session is bound to this thread.", but the box stays open. Every submit just adds another failure.
  • The popup can never be dismissed. The client only closes a pending user-input request on a user-input.resolved activity or when a failure detail matches the "stale pending request" pattern. That detail does not match, so the request stays open indefinitely.

Related but distinct issues: #4060 (desktop, stuck on user input GUI) and #5119 (Claude adapter leaking pending requests on teardown). This one is provider-agnostic and also triggers on a plain server restart.

Steps to reproduce

  1. Have an agent call request_user_input so the question card renders in the composer.
  2. Restart the T3 server (or let the provider process exit) while the card is pending — the thread session becomes stopped.
  3. Type an answer and submit → error No active provider session is bound to this thread., and the card stays open.
  4. The card can never be closed, and the thread stays unsettleable.

Expected behavior

Once the provider session is gone, the pending request is unanswerable and should be treated as terminal (like the existing Unknown pending user-input request stale path), so the popup closes and the thread can be settled or restarted.

Fix

PR #5453 treats the "No active provider session is bound to this thread." respond-failure detail as a stale pending request in the matchers shared by the web, mobile, server decider, and projection pipeline.

Activity

  1. kevinavos commented on Aug 13, 2026

    @kevinavos

    Hit the same stuck-composer failure on a pending approval (not user-input) with the Cursor provider.

    What happened

    T3 Code 0.0.33 on Linux. Thread was Cursor auto-smart in Full access. An MCP approval for t3-code-preview_navigate stayed in the composer after the Cursor ACP session died (provider.stopAll on server restart; projected session stopped).

    Cancel / Approve both fail with:

    Provider approval response failed
    No active provider session is bound to this thread.
    

    The composer stays locked on that card, so a new prompt cannot be sent. Cancel does not emit approval.resolved. The failure detail is not treated as stale, so hasOpenBlockingRequest / derivePendingApprovals keep the request open. This thread accumulated 50+ provider.approval.respond.failed activities on the same requestId.

    projection_pending_approvals can already show resolved while the UI still blocks, because the web client derives the open card from activities (approval.requested with no approval.resolved and no matching stale-failure detail).

    Workaround that unblocked it

    Starting a live provider session (a new thread.turn.start) and then responding again produced:

    Stale pending approval request: <requestId>. Provider callback state does not survive app restarts or recovered sessions.
    

    That string does match the stale matcher, and the composer unlocked.

    Why this comment

    #5454 is titled around user-input, but the same short-circuit and matcher gap applies to approvals. PR #5453 already says it covers both; this is a Cursor + approval repro of that path. Please consider broadening the issue title so approval-blocked composers are searchable.

    Happy to provide more logs if useful.

  2. letherman255 commented on Sep 13, 2026

    @letherman255

    Same bug on t3 v0.0.40, remote SSH environment (desktop app 0.0.40 on Windows 11, t3 serve on Ubuntu over SSH, Claude Agent provider). Adding the server-side evidence, since it pins down where the resolution is lost and shows there is currently no in-UI recovery.

    Timeline from state.sqlite (orchestration_events) and logs/provider/events.<thread>.log:

    UTC Event
    09-12 21:32:45 user-input.requested activity appended (AskUserQuestion, 3 questions, request 155e3af1…)
    09-12 22:49:55 Server going down for a restart. Provider log has CANON {"type":"user-input.resolved", "requestId":"155e3af1…", "payload":{"answers":{}}, "raw":{"method":"canUseTool/AskUserQuestion/resolved"}} followed by turn.completed {"state":"interrupted","errorMessage":"Session stopped."} and session.exited
    09-12 22:50:02 New server Listening. Thread auto-continued (continueThreadsAfterServerUpdate: true), ran two more turns, ended 09-13 01:24 with a final assistant message. Idle session reaped 01:25:04.
    09-13 04:20 → 04:33 9 × thread.user-input-response-requested from the client, each answered by provider.user-input.respond.failed / No active provider session is bound to this thread.

    The user-input.resolved canonical event at 22:49:55 was written to the provider log but never appended to orchestration_events. The last user-input.resolved activity for that thread in the event store is from 18:28 (an earlier request). Since derivePendingUserInputCountFromActivities only closes a request on user-input.resolved or on the specific "stale/unknown pending user-input request" detail strings, pending_user_input_count stayed at 1 through two further server restarts.

    Why the user is locked out (0.0.40):

    1. While a card is pending, the composer sends the typed text as thread.user-input.respond (answers keyed by question), not as thread.turn.start. A normal message therefore never reaches ensureSessionForThread.
    2. processUserInputResponseRequested returns the "No active provider session" failure when thread.session.status === "stopped" and never starts a session, so the respond path can never succeed once the reaper has run.
    3. thread.user-input.dismiss rejects with "This question needs an answer. Answer it or stop the turn." unless responseMode === "message", so question cards cannot be dismissed.
    4. thread.turn.interrupt hits the same "No active provider session" guard.

    Workaround that worked (no DB edits, no restart): issue a token with t3 auth session issue --base-dir … --ttl 20m --json, POST a thread.turn.start command to /api/orchestration/dispatch for the thread (this spawns a fresh provider session with --resume), then POST a thread.user-input.respond for the stale request id while the session is live. The adapter answers Stale pending user-input request: … Provider callback state does not survive app restarts or recovered sessions., which the projection counts as resolved, and the card disappears. Revoke the token afterwards.

    Two fixes would each be enough: treat the "No active provider session" detail as terminal in the projection matchers (as #5453 proposed), or have the respond path call ensureSessionForThread like a normal turn start does.

  3. MaximilianMauroner commented on Sep 16, 2026

    @MaximilianMauroner

    Confirmed on macOS desktop 0.0.43-nightly.20260916.1811 with OpenCode.

    A valid user-input.requested activity remained pending after provider.stopAll left the projected session stopped. Three answer attempts each produced:

    No active provider session is bound to this thread.
    

    Each attempt appended another thread.user-input-response-requested and provider.user-input.respond.failed pair. pending_user_input_count remained 1, so the composer stayed blocked.

    The documented recovery of starting a resumed turn and then answering the stale callback worked, with one timing detail:

    1. Dispatching thread.turn.start changed the session to starting.

    2. Answering immediately failed with:

      OpenCode is still loading this question. Try again.
      

      The request remained pending.

    3. Waiting until the session reached running and answering again produced:

      Stale pending user-input request: <requestId>. Provider callback state does not survive app restarts or recovered sessions.
      
    4. pending_user_input_count then became 0, the card closed, and the resumed turn continued with the user’s answers.

    This supports the approach in #10586. Native questions should be resolved when their provider session exits. If the manual resume workaround remains necessary, it must wait until the adapter is fully running rather than only starting.

    Investigated with GPT-5.6 Sol via Codex in T3 Code.

  4. eli-jordan commented on Sep 29, 2026

    @eli-jordan

    Confirmed another occurrence on macOS T3 Code Nightly 0.0.43-nightly.20260929.2428 with OpenCode 1.18.33, this time after the OpenCode run was aborted while a blocking two-question question call was open (not just a server restart).

    Observed sequence in the local event store:

    1. user-input.requested at 13:27:07 UTC.
    2. thread.session-set → error (lastError: "Aborted") at 13:27:45; the question tool completed as failed / Tool execution aborted; session → stopped at 13:27:47.
    3. Answer attempts from 13:27:52 through 13:36:42 each appended provider.user-input.respond.failed with No active provider session is bound to this thread. The thread projection remained pending_user_input_count = 1 and the composer would not allow a new message.
    4. The underlying OpenCode session still answered HTTP 200 and its resume cursor was intact, but the adapter had no live callback. T3’s thread.user-input.dismiss path rejects a blocking question unless responseMode === "message" (This question needs an answer. Answer it or stop the turn.); the turn was already stopped.

    The user was unblocked only by recording a user-input.resolved activity for the orphaned request in the local event store/projection. No project files were changed. This is the same no-session stale-request handling gap described here, with Aborted as an additional trigger. It would help if stopping/aborting a provider run also terminally resolved its pending blocking question, or if the UI offered a recovery path when the session is stopped.

  5. cornishandy commented on Oct 1, 2026

    @cornishandy

    Still reproduces on 0.0.45-nightly.20260930.2468 (macOS, desktop, Claude Agent provider). Two details that may be new:

    1. The trigger was the restart continuation, and it stacked two orphaned cards. Timeline (UTC):

    • 09-30 03:53: AskUserQuestion opens request A in turn T1. The session then dies with A open; T1 never gets a terminal event.
    • 09-30 13:37: restart continuation sends "Continue where you left off." as a new turn T2 (no thread.turn-start-requested event). The model sees its interrupted tool call and asks again: request B in T2.
    • 10-01 03:09: same again, turn T3. The model gives up on the tool and asks in plain text. T3 completes normally, but the terminal-turn cleanup only resolves requests whose turnId equals T3, so A and B both stay pending (pending_user_input_count = 2).
    • 10-01 03:44: session -> stopped. Four answers to A at 05:03-05:04 each fail with No active provider session is bound to this thread.

    2. No startup reconciliation in this build. The bundled apps/server/dist/bin.mjs has no reconcileOrphanedNativeUserInputs and no startup:user-input-resolved command ids (#14190 found them in 0.0.43-nightly.20260927, not firing). The only automatic cleanup left is terminal-user-input-resolved, keyed on the terminal turn's id, which a continuation turn never matches.

    Workaround from @letherman255's comment worked unchanged: thread.turn.start via /api/orchestration/dispatch, wait for running, then respond to each stale id -> Stale pending user-input request, count 0.

    Side note if the fix converts orphaned native questions to responseMode: "message": that branch of thread.user-input.respond requires every answer to be a string, but the web client sends multiSelect answers as arrays, so a converted multi-select AskUserQuestion would fail with "Answer each question before sending."

    Investigated with Claude Code (Opus 5.5).

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