Metadata & Triage
- Type: Bug (
bug)
- Area:
apps/server, apps/web
- Estimated Fix Size:
size:S (10–29 lines in provider interrupt / task completion cleanup)
- Impact: Major degradation or frequent failure
Before submitting
Steps to reproduce
- Run a thread where a background command or watch loop (e.g., monitor task, background shell, or log tail) outlives the turn.
- Once the assistant turn finishes and settles, the thread shows a
Monitoring pill in the sidebar and a • Monitoring banner with a Stop button above the composer.
- Click the
Stop button on the banner.
Expected behavior
The background tasks should be stopped/interrupted, task.completed or terminal status should be emitted for the running background tasks, the background liveness should clear from the thread, and the Monitoring / Stopping... banner should disappear.
Actual behavior
The button switches to Stopping..., but the thread remains stuck in Monitoring indefinitely. The Stopping... state never clears.
Screenshots
1. Thread showing "Monitoring" badge in sidebar and composer banner with "Stop" button:

2. Stuck on "Stopping..." indefinitely after clicking Stop:

Root Cause Analysis
- In
apps/web/src/components/ChatView.tsx, handleStopBackgroundWork calls interruptThreadTurn under the assumption that it tears down live background work across the thread:
// Background work (subagent fleets, workflow runs, watch loops) can outlive
// the turn; 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: it kills every live background task before
// interrupting, and works by session, so no active turn is needed.
isStoppingBackgroundWork holds "Stopping..." until activeBackgroundLiveness === null.
- When
interruptThreadTurn is called after a turn has settled, turnId is undefined.
- In
ProviderCommandReactor.ts, the command routes to providerService.interruptTurn({ threadId: event.payload.threadId }).
- If the provider adapter's
interruptTurn implementation only handles active prompt execution (cancelling active generation / pending approvals) rather than terminating lingering background tasks/commands or emitting terminal task states, ThreadBackgroundLivenessService is never notified that the background task ended.
- Because no
task.completed or terminal event is received, ThreadBackgroundLivenessService keeps the task in its monitors set.
- As a result,
activeBackgroundLiveness remains "monitoring", the sidebar badge remains Monitoring, and the composer banner stays stuck in Stopping....
Note on provider implementation: This was observed with the Antigravity provider, where AntigravityAdapter.ts only calls context.runtime.cancel and cancels pending requests inside interruptTurn, but does not clean up context.commands or invoke finishBackgroundCommands(context).
Version or commit
Latest upstream main @ b1e223e2b (v0.0.41-nightly.20260912.1599)
Environment
Desktop / Web app
Metadata & Triage
bug)apps/server,apps/websize:S(10–29 lines in provider interrupt / task completion cleanup)Before submitting
Steps to reproduce
Monitoringpill in the sidebar and a• Monitoringbanner with aStopbutton above the composer.Stopbutton on the banner.Expected behavior
The background tasks should be stopped/interrupted,
task.completedor terminal status should be emitted for the running background tasks, the background liveness should clear from the thread, and theMonitoring/Stopping...banner should disappear.Actual behavior
The button switches to
Stopping..., but the thread remains stuck inMonitoringindefinitely. TheStopping...state never clears.Screenshots
1. Thread showing "Monitoring" badge in sidebar and composer banner with "Stop" button:

2. Stuck on "Stopping..." indefinitely after clicking Stop:

Root Cause Analysis
apps/web/src/components/ChatView.tsx,handleStopBackgroundWorkcallsinterruptThreadTurnunder the assumption that it tears down live background work across the thread:isStoppingBackgroundWorkholds"Stopping..."untilactiveBackgroundLiveness === null.interruptThreadTurnis called after a turn has settled,turnIdis undefined.ProviderCommandReactor.ts, the command routes toproviderService.interruptTurn({ threadId: event.payload.threadId }).interruptTurnimplementation only handles active prompt execution (cancelling active generation / pending approvals) rather than terminating lingering background tasks/commands or emitting terminal task states,ThreadBackgroundLivenessServiceis never notified that the background task ended.task.completedor terminal event is received,ThreadBackgroundLivenessServicekeeps the task in itsmonitorsset.activeBackgroundLivenessremains"monitoring", the sidebar badge remainsMonitoring, and the composer banner stays stuck inStopping....Version or commit
Latest upstream
main @ b1e223e2b(v0.0.41-nightly.20260912.1599)Environment
Desktop / Web app