Skip to content

[Bug]: Mobile has no way to stop background work after the turn settles #14655

Description

@tris203

Before submitting

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

Area

apps/mobile

Steps to reproduce

  1. On a Claude thread, ask the agent to start something that outlives the turn: a Monitor-tool watch loop, a background shell, or background subagents.
  2. Wait for the parent turn to finish. On web/desktop the composer now shows a Monitoring (or N agents working) banner with a Stop button.
  3. Open the same thread in the mobile app (iOS or Android) and try to stop the background work.

Expected behavior

Mobile offers a Stop control for background work that outlives the turn, the same as the web/desktop banner. Pressing it stops the background tasks and the thread returns to idle.

Actual behavior

Mobile has no stop control in this state. The composer shows the normal send/dictation actions, and nothing else on the thread screen can stop the work. The only way to stop it is to switch to web or desktop.

This is separate from the missing status label tracked in #10372 and #4962. #13803 fixes those by adding the Working/Monitoring label to the mobile list and the open-thread pill, but its diff has no stop or interrupt changes, so after it merges mobile will show Monitoring and still not be able to stop it.

Impact

Major degradation or frequent failure

It happens every time background work outlives a turn, and the phone is often the only client at hand when a user wants to stop a runaway watch loop or subagent fleet.

Version or commit

main @ 094fb23

Environment

Source-level finding against main; not reproduced on a device for this report. The code involved is shared React Native code, so it applies to both iOS and Android. The device reports on #10372 and #4962 (iOS, Pixel 9, Galaxy S25 Ultra) describe the same thread state.

Source evidence

Web deliberately gives this state its own stop control, and says why:

  • ChatView.tsx#L6237-L6243: "once it settles, the composer stop button is gone, so this banner is the only visible stop affordance. Stop routes through the stop-everything interrupt ... and works by session, so no active turn is needed."
  • ChatView.tsx#L6278-L6313: the banner and its Stop button.
  • ChatView.logic.ts#L530-L539: the interrupt input is built with threadId only when no turn is running.

Mobile blocks the same action in two places:

  • ThreadComposer.tsx#L301-L304: showStopAction is true only while session.status is running or starting, so the "Stop agent" button is hidden once the turn settles.
  • ThreadRouteScreen.tsx#L632-L649: handleStopThread returns early unless the session is running or starting. Showing a button is therefore not enough; the handler also has to allow a settled session with background work.

The server side already supports it. backgroundLiveness is on the thread shell mobile receives (orchestration.ts#L928), and the Claude adapter's interrupt stops the whole session regardless of turn (ClaudeAdapter.ts#L5363-L5371). No contract or server change should be needed.

apps/mobile has no references to backgroundLiveness on main.

Suggested fix

When backgroundLiveness is non-null and no turn is running, show a Stop control on the mobile thread screen and send the interrupt with threadId only, as buildThreadTurnInterruptInput does on web. Hold a "Stopping..." state until the liveness clears, since the command returning only means the request was accepted.

Workaround

Open the thread on web or desktop and press Stop on the Monitoring banner.

Related

Investigated from source with Claude Opus 5.5 in T3 Code.

Activity

  1. juliusmarminge commented on Oct 1, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks for tracing this through both clients, @tris203. Your source links held up exactly. I confirmed it on current main (094fb230), the commit you cited. Once the parent turn settles, web still offers a Stop control for leftover background work, but mobile doesn't. That applies to both iOS and Android, since the gate lives in shared React Native code.

    Why web can stop it

    Web shows the Monitoring banner only after the turn stops working, because by then the composer's Stop button is gone. Pressing its Stop sends thread.turn.interrupt with only threadId when the session isn't running (buildThreadTurnInterruptInput in apps/web/src/components/ChatView.logic.ts). The server accepts that command without a running turn (apps/server/src/orchestration/decider.ts), and ProviderCommandReactor forwards it by session unless the session is missing or stopped. Claude's interruptTurn ignores the turn id and closes the session.

    Why mobile can't

    • apps/mobile never reads backgroundLiveness, even though the thread shell the app receives already has it (EnvironmentThreadShell extends OrchestrationThreadShell).
    • The composer's Stop button only appears when there's no draft and session.status is running or starting (ThreadComposer.tsx).
    • handleStopThread returns right away for any other status (ThreadRouteScreen.tsx), so showing a button wouldn't be enough on its own.

    After a turn settles, the session is ready while backgroundLiveness is working or monitoring. In that state the composer only offers Send and dictation, and nothing else on the thread screen calls the interrupt. I checked this in source rather than on a device, but none of these gates depend on the platform.

    Not a duplicate

    Fix direction

    On the open thread, when backgroundLiveness is non-null and no turn is running, show a Stop control that sends the same thread-only interrupt web sends. Unlike the running-turn button, which is hidden whenever the composer has text, it should stay visible when there's a draft. Showing "Stopping…" until the liveness clears is a good idea, since the command returning only means the request was accepted.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 1, 2026
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

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions