Skip to content

[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking #8618

Description

@ImBIOS

Before submitting

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

Area

apps/web • apps/mobile • packages/client-runtime • apps/server

Description

Using a remote T3 Code server, clicking the Stop button gives no UI feedback and the thread stays stuck in Thinking / Working for Xm Ys indefinitely.

Screenshot attached: prompt list me all ~/dev/projects/*Working for 6m 26s + Thinking, bottom composer shows red stop still active, environment myrehat-dev.asia-southeast1-a.c.myrehat-... · Local checkout · main, model Muse Spark 1.2 Contrib..., Xhigh · Build, Full access.

Expected: Stop shows Stopping... immediately (disabled) and within seconds drops Thinking/Working, even on relay/tunnel. Actual: no visual change, subsequent Stop clicks also silent, thread never recovers until server restart or manual projection_thread_sessions stopped + projection_turns interrupted repair.

Steps to reproduce (remote)

  1. Start remote server (npx t3 --share / --tunnel / npx t3 behind Tailscale on myrehat-dev...).
  2. Connect from app.t3.codes or mobile via pairingUrl: token (remote mode, not local).
  3. Open thread with Local checkout, send a prompt that streams (list me all ~/dev/projects/* and it…, or any long agent run).
  4. While Thinking/session.status === "running", click the red Stop button in the composer.

Actual

  • Button does not morph to Stopping..., not disabled, no toast, no spinner change (100–400ms relay RTT makes it obvious; local 10–20ms masks it).
  • Timeline keeps Working for ... ticking forever.
  • DB: projection_thread_sessions.status = 'running' with active_turn_id pinned to a turn already interrupted/completed (completed_at set), or NULL with pending turn forever (#4713 detection query hits):
    SELECT s.thread_id, tu.state, tu.completed_at
      FROM projection_thread_sessions s
      JOIN projection_turns tu ON tu.turn_id = s.active_turn_id
     WHERE s.status = 'running'
       AND tu.state IN ('interrupted','completed','error')
       AND tu.completed_at IS NOT NULL;
    Widened query also catches active_turn_id IS NULL + pending turn (report at 2026-08-24T23:30:31Z in Thread session stuck in running after turn interrupt — stop button becomes a no-op #4713).
  • Every further Stop appends thread.turn-interrupt-requested in orchestration_events (accepted in orchestration_command_receipts) but produces no follow-up thread.session-set; the stop handler has nothing to act on.

Expected

  • Optimistic Stopping... within one frame on every surface (web composer, mobile ThreadRouteScreen), disabled until terminal session-set stopped/interrupted or error.
  • Even when buildThreadTurnInterruptInput omits turnId (stale activeTurnId), the client and projection still flip latestTurn.state → interrupted optimistically.
  • If provider interrupt succeeds yet CLI stays alive, server escalates after ~5s to stopSessionsession-set stopped, so derivePhasedisconnected/ready and MessagesTimeline clears Working.
  • Relay blips surface as toast instead of silent no-op (reportFailure:false currently swallows EnvironmentRpcUnavailableError while phase === "available"|"offline").

Evidence & analysis

Investigated in worktree t3code-a1ef8329 (6a9d9f9):

  • UI: apps/web/src/components/ChatView.tsx:1246 interruptThreadTurn = useAtomCommand(threadEnvironment.interruptTurn, {reportFailure:false}), 5487-5510 onInterrupt has no isStoppingTurn state. Contrast handleStopBackgroundWork 4677-4740 which correctly tracks isStoppingBackgroundWork, disables Button 4543 and shows "Stopping..." 4546.
  • Input guard: ChatView.logic.ts:141-150 buildThreadTurnInterruptInput includes turnId only if session.status==="running" && activeTurnId!==null. Remote snapshots lag (THREAD_RESUME_MAX_GAP 1000, SHELL_COALESCE_WINDOW 50ms ws.ts:841), so Stop often persists thread.turn-interrupt-requested without turnId. Both threadReducer.ts:271-292 and ProjectionPipeline.ts:1441-1476 return unchanged/no-op for turnId===undefined.
  • Projection: session-logic.ts:1894-1905 derivePhase = running iff session.status==="running"|"starting"; MessagesTimeline.tsx:210,1313 WorkingTimelineRow only clears when isWorking===false. threadReducer latestTurn.state==="interrupted" alone does not clear it.
  • Server reactor: ProviderCommandReactor.ts:1228-1321 only tears down on failure (recoverInterruptFailurestopSession). On success it waits for provider session-set. When ClaudeAdapter.ts:query.interrupt() never settles / OpenCodeAdapter.ts:2932 10s abort times out / CodexAdapter ignores, session never leaves running. Serial interruptTurn (threadCommands.ts:177-182 concurrency:{mode:"serial"} per (env,thread)) then queues further clicks.
  • Family: pingdotgg/t3code#4713 (40+ accepted interrupts no effect, SIGTERM workaround), #4589 (threads stop updating), #7820, #2644, #7349 (bounded Claude query.interrupt 10s + getContextUsage 1s — but only for failure path), #7589, #7987.

Impact

Major: remote is primary use (Tailscale/tunnel/mobile). User cannot cancel, cannot archive (useThreadActions.ts:214 refuses archive while running), mobile outbox gates on running (use-thread-outbox-drain.ts:317) so queued messages never deliver. Only way out is SIGTERM --resume <cursor> or manual DB session-set stopped + restart.

Environment

Suggested fix (non-negotiables)

  1. Web + mobile optimistic isStoppingTurn (mirror isStoppingBackgroundWork) → Stopping... disabled.
  2. Relax turnId===undefined guard to fallback to activeTurnId ?? latest unsettled turn.
  3. Server 5s fallback stopSession when interrupt succeeds but session still running (success-still-running), with bounded getContextUsage.
  4. interruptTurn timeout / singleFlight so promise doesn't hang; surface transport errors for Stop.
  5. Tests + projector coverage.

Logs

  • state.sqlite orchestration_events thread.turn-interrupt-requested accepted without follow-up thread.session-set.
  • projection_thread_sessions running + projection_turns interrupted/pending as above.

Managed via forkhub as fix-remote-stop-no-feedback-stuck-thinking-7h3k9p2m. See ImBIOS/.forkhub INTENT.md for full non-negotiables and implementation notes.

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