[Bug]: Remote stop button gives no feedback and leaves thread stuck in Thinking
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 23k
- Forks
- 5.9k
- Avg merge
- 11h 14m
- Merged PRs (30d)
- 357
Description
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)
- Start remote server (
npx t3 --share/--tunnel/npx t3behind Tailscale onmyrehat-dev...). - Connect from
app.t3.codesor mobile viapairingUrl:token (remote mode, not local). - Open thread with Local checkout, send a prompt that streams (
list me all ~/dev/projects/* and it…, or any long agent run). - 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'withactive_turn_idpinned to a turn alreadyinterrupted/completed(completed_atset), orNULLwith pending turn forever (#4713detection query hits):
Widened query also catchesSELECT 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;active_turn_id IS NULL+pendingturn (report at2026-08-24T23:30:31Zin #4713). - Every further Stop appends
thread.turn-interrupt-requestedinorchestration_events(acceptedinorchestration_command_receipts) but produces no follow-upthread.session-set; the stop handler has nothing to act on.
Expected
- Optimistic
Stopping...within one frame on every surface (web composer, mobileThreadRouteScreen), disabled until terminalsession-setstopped/interruptedor error. - Even when
buildThreadTurnInterruptInputomitsturnId(staleactiveTurnId), the client and projection still fliplatestTurn.state → interruptedoptimistically. - If provider interrupt succeeds yet CLI stays alive, server escalates after ~5s to
stopSession→session-setstopped, soderivePhase→disconnected/readyandMessagesTimelineclearsWorking. - Relay blips surface as toast instead of silent no-op (
reportFailure:falsecurrently swallowsEnvironmentRpcUnavailableErrorwhilephase === "available"|"offline").
Evidence & analysis
Investigated in worktree t3code-a1ef8329 (6a9d9f9):
- UI:
apps/web/src/components/ChatView.tsx:1246interruptThreadTurn = useAtomCommand(threadEnvironment.interruptTurn, {reportFailure:false}),5487-5510onInterrupthas noisStoppingTurnstate. ContrasthandleStopBackgroundWork4677-4740which correctly tracksisStoppingBackgroundWork, disablesButton4543and shows"Stopping..."4546. - Input guard:
ChatView.logic.ts:141-150buildThreadTurnInterruptInputincludesturnIdonly ifsession.status==="running" && activeTurnId!==null. Remote snapshots lag (THREAD_RESUME_MAX_GAP 1000,SHELL_COALESCE_WINDOW 50msws.ts:841), so Stop often persiststhread.turn-interrupt-requestedwithoutturnId. BoththreadReducer.ts:271-292andProjectionPipeline.ts:1441-1476returnunchanged/no-op forturnId===undefined. - Projection:
session-logic.ts:1894-1905derivePhase=runningiffsession.status==="running"|"starting";MessagesTimeline.tsx:210,1313WorkingTimelineRowonly clears whenisWorking===false.threadReducerlatestTurn.state==="interrupted"alone does not clear it. - Server reactor:
ProviderCommandReactor.ts:1228-1321only tears down on failure (recoverInterruptFailure→stopSession). On success it waits for providersession-set. WhenClaudeAdapter.ts:query.interrupt()never settles /OpenCodeAdapter.ts:293210s abort times out /CodexAdapterignores, session never leavesrunning. SerialinterruptTurn(threadCommands.ts:177-182concurrency:{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 Claudequery.interrupt10s +getContextUsage1s — 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
- T3 Code
v0.0.37-nightly.20260829.12186a9d9f9(local) / remotemyrehat-devLinux aarch64, provideropencodeMuse Spark 1.2 Contrib... - Client
app.t3.codes/ desktop wrap, remote relay. - Also reproduced for
claudeAgent, Codex, Cursor per #4713.
Suggested fix (non-negotiables)
- Web + mobile optimistic
isStoppingTurn(mirrorisStoppingBackgroundWork) →Stopping...disabled. - Relax
turnId===undefinedguard to fallback toactiveTurnId ?? latest unsettled turn. - Server 5s fallback
stopSessionwhen interrupt succeeds but session stillrunning(success-still-running), with boundedgetContextUsage. interruptTurntimeout /singleFlightso promise doesn't hang; surface transport errors for Stop.- Tests + projector coverage.
Logs
state.sqliteorchestration_eventsthread.turn-interrupt-requestedacceptedwithout follow-upthread.session-set.projection_thread_sessionsrunning+projection_turnsinterrupted/pendingas 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with apps/web/src/components/ChatView.tsx and ChatView.logic.ts, then compare threadReducer.ts and ProjectionPipeline.ts handling of a missing turnId. Trace ProviderCommandReactor.ts and session-logic.ts for the interrupt/session lifecycle, including ThreadRouteScreen on mobile. Done means responsive stop feedback, recovery from stale remote state, bounded server behavior, surfaced failures, and passing added tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- backend, distributed-systems, frontend, mobile
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 35/100