Repository navigation
Description
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate.
Area
apps/mobile • packages/client-runtime
Description
Mobile Stop does nothing during Setting up worktree… (thread-creation preparing phase).
Follow-up to #8618 (remote Stop gives no feedback / stuck in Thinking, fixed by #8619). That issue/PR covers the Thinking/Working phase where session.status is running/starting. This report covers the earlier window: a new task with workspaceMode: "worktree" shows Setting up worktree… while the creation is still queued or the server is still checking out the worktree — and Stop is either hidden or a silent no-op.
Reproduced against remote server myrehat-dev (Tailscale/relay) in a thread like "Chatwoot and Human Takeover": tap Stop while the pill reads Setting up worktree… → nothing happens immediately, no Cancelling…/Stopping… feedback, worktree setup continues.
Steps to reproduce
- On mobile, connected to a remote environment, create a new task with worktree mode (new branch).
- While the floating pill reads
Setting up worktree…(creationState.kind === "preparing",preparingWorktree === true), tap the Stop control. - Observe: no immediate feedback; the creation continues.
Actual
ThreadRouteScreen.handleStopThreadearly-returns unlessselectedThread.session?.statusisrunning/starting(ThreadRouteScreen.tsx:501-508). During preparing, the stand-in shell frompendingThreadCreationShellhassession: null(pending-thread-creation.ts:159), so Stop is a no-op.ThreadComposer.showStopActionis also gated onsession running/starting(ThreadComposer.tsx:308-311), so the stop affordance may not even render during the queued window.- Even when a server shell has arrived with
starting, there is no active turn yet (latestTurn === null, empty detail during checkout), sothread.turn.interrupthas nothing to act on server-side (ProviderCommandReactor.processTurnInterruptRequested→ "No active provider session"). - There is no path to cancel the queued creation from the thread screen (remove the outbox entry + clear the pending-creation outcome); the drain keeps it and delivers it.
Expected
- Stop during
preparinggives feedback within one frame (Cancelling…/Stopping…, disabled) on every surface that renders the pill/composer. - If the creation is still queued locally, Stop cancels it: remove the outbox message (compare-and-set, respecting
editingQueuedMessageIdsAtom), clearpendingThreadCreationOutcomesAtomfor the thread key, and return to a sane screen (draft restored or back). - If the creation was already delivered (server thread exists, worktree checking out, no turn yet), Stop still sends the best-effort interrupt/stop AND surfaces that the setup cannot be cancelled mid-checkout if that is the decided behavior — never a silent no-op.
- Relay/offline failures surface as a toast instead of silence.
Evidence & analysis
Investigated on main (cdfeb1d):
apps/mobile/src/features/threads/ThreadRouteScreen.tsx:501handleStopThreadguard;:785-800creationStatepreparing derivation.apps/mobile/src/features/threads/ThreadComposer.tsx:308showStopActionguard;:805,896stop buttons.apps/mobile/src/state/pending-thread-creation.ts:132-165stand-in shellsession: null;:28-68resolvePendingThreadCreationretention.apps/server/src/orchestration/Layers/ProviderCommandReactor.ts:1586-1600interrupt with no session → failure activity only.- fix(remote): stop button gives immediate feedback and prevents stuck thinking on remote server #8619 adds
isStoppingThreadoptimistic UI but keeps the samerunning/startinggate, so preparing is still uncovered.
Environment
- Mobile client (iOS/Android) → remote T3 server
myrehat-devover Tailscale/relay. - New task with worktree checkout (
Setting up worktree…).
Suggested fix (non-negotiables)
- Mobile
handleStopThread: branch oncreationState.kind === "preparing"— cancel queued creation locally (outbox removal + clear outcome) with immediate optimistic state; if already delivered, fall through to interrupt withisStoppingThreadfeedback. - Render a stop affordance during
preparing(not justrunning/starting), disabled +Cancelling…while the cancel settles. - Guard the outbox race: compare-and-set removal, respect in-edit messages (defer, don't drop user edits).
- Tests:
pending-thread-creation+ outbox-removal unit coverage for cancel-during-preparing; no repo-wide checks.
Activity
juliusmarminge commentedon Sep 17, 2026
Triage
Confirmed on current
main(901db8966). This is a real mobile Stop bug during the thread-creationpreparingwindow, not a remote-Thinking hang and not a missing setup-progress card. Not already fixed. Not a duplicate of #8618.What you reported. On mobile, connected to a remote environment, a new worktree task shows
Setting up worktree…. Tapping Stop does nothing: noCancelling…/Stopping…, and setup continues. You traced it tohandleStopThreadandshowStopActionrequiringsession.statusrunning/starting, while the pending-creation stand-in hassession: nulland there is no thread-screen path to cancel a still-queued creation.That matches the code.
What the code does
1. Composer Stop is still gated on a live session.
ThreadComposer.showStopAction(apps/mobile/src/features/threads/ThreadComposer.tsx:304-307) issession.status === "running" | "starting"and an empty draft. During preparing,pendingThreadCreationShellsetssession: null(apps/mobile/src/state/pending-thread-creation.ts:159), so the Stop affordance does not render. The floating pill is display-only (floating-working-control.tsx: only the reconnect label is tappable).2.
handleStopThreadis a silent no-op in that window.ThreadRouteScreen.handleStopThread(ThreadRouteScreen.tsx:542-548) returns unless the selected shell isrunning/starting. There is noisStoppingThreadon mobile (or web). #8619 is still open and does not land that optimistic UI.3. A queued creation cannot be cancelled from the thread screen.
The stand-in is kept by
resolvePendingThreadCreationuntil the prompt is in detail. Home can delete a pending task (usePendingTaskListActions→removeThreadOutboxMessage). The thread screen has no equivalent: it does not remove the outbox row or clearpendingThreadCreationOutcomesAtom. The drain keeps delivering.4. #12177 added a different cancel surface; it does not fix this.
feat(mobile): port worktree setup progress and agent handoff(4abb07eac, merged today) hides the preparing pill once a worktree-setup snapshot exists and puts Cancel setup / Work locally in the Details sheet (worktree-setup-card.tsx:210-264). It also remapsonStopThreadtohandleCancelWorktreeSetupwhileawaitingBootstrapTurn(ThreadRouteScreen.tsx:837-846, 1053). That remapping is dead for the composer:showStopActionis still false, so Stop is never shown to invoke it.handleCancelWorktreeSetuponly RPCsvcs.worktreeSetupCancelafter a live/recorded snapshot — it does not cancel a still-queued outbox creation.handleCancelWorktreeSetupisvoidwith no optimistic disabled/Cancelling…state.5. Interrupt is the wrong tool before a turn exists.
Once a server shell exists with no session (or
startingandlatestTurn === null),thread.turn.interrupthitsProviderCommandReactor.processTurnInterruptRequested(ProviderCommandReactor.ts:1514-1522) and recordsprovider.turn.interrupt.failed/ “No active provider session is bound to this thread.” Server cancel settlement for an in-flight checkout is already #12176 (worktreeSetupCancel).Related, not duplicates
sessionrunning/starting). Different window.worktreeSetupCancelbefore rollback. Needed by the delivered/checkout path; does not show Stop.Suggested smallest scope
Stay on mobile thread create. Do not wait for #8619. Do not change web. Do not add OpenCode sanitizer work.
creationState.kind === "preparing"(composer, and any other surface that owns the preparing pill). Immediate optimisticCancelling…/Stopping…, disabled until settle.null, not the in-flightdispatchingMessageId):removeThreadOutboxMessagecompare-and-set, respecteditingQueuedMessageIdsAtom, clearpendingThreadCreationOutcomesAtom, leave a sane screen (restored draft or back).phase === "running": call the existingcancelWorktreeSetuppath (fix(server): settle cancelled worktree setup before rollback #12176/feat(mobile): port worktree setup progress and agent handoff #12177), notthread.turn.interrupt. If product decides mid-checkout cannot be cancelled, say so in the UI — never a silent no-op.pending-thread-creation+ outbox removal). No repo-wide checks.Open PR
#12188 (
fix(mobile): make Stop work during preparing worktree phase) is the same author andFixes #12187. The mobile slice is the right idea (resolvePreparingStopAction, composer Stop during preparing, outbox cancel,isStoppingThread).Do not review that PR as-is. It is
size:L/vouch:unvouchedand mixes an unrelated OpenCode OSC/ANSI sanitizer (stripTerminalEscapes.ts,OpenCodeAdapter/OpenCodeProvider/opencodeRuntime,packages/shared/src/model.ts). CONTRIBUTING: do not mix unrelated fixes. Same problem as #12186.The helper also returns
noopfor a delivered creation withsession: null, and the interrupt branch does not usecancelWorktreeSetupfor an in-flight checkout.Workaround
Setting up worktree…pill.Next step
Accepted. Keep this issue open until a mobile-only PR lands. Split the OpenCode sanitizer out of #12188 (or open a fresh PR with just preparing Stop) before review.