Skip to content

fix: sending after Stop no longer strands held queued messages - #18809

Closed
TinBane wants to merge 1 commit into
pingdotgg:mainfrom
TinBane:fix/held-queue-resumes-on-send
Closed

TinBane wants to merge 1 commit into
pingdotgg:mainfrom
TinBane:fix/held-queue-resumes-on-send

Conversation

@TinBane

@TinBane TinBane commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Problem

After Stop, messages queued behind the stopped run are held so that nothing restarts on its own. If you then send a new message, it starts at once, but the held messages stay held. When the new turn ends, the queue never drains, because the next-message check skips the queue while anything is held. Steer is disabled because nothing is running, and the web queue just says "Queued", so nothing tells you the message is paused or that Resume in an empty composer is the way out. Fixes #18805.

Change

  • Server: a new message someone types into web or mobile now releases the held queue, so the held messages run after it. This covers both start paths, including a provider switch. The hold stays in place for:
    • sends that are not a person acting now: scheduled prompts (they carry the client their task was created from), re-sent late steers (they reuse an existing message), usage-limit auto-resume, and agent or MCP sends.
    • messages held because their worktree was never created. Releasing those would let them run in the project checkout once the new run ends. Retrying the setup still releases them, as before.
  • Web: the queue header reads Paused while the queue is held.
  • Mobile: the sheet's banner said "Queue held after restart" for every hold. It now says "Queue paused", next to its existing Resume queue button.
  • Docs: the queued-messages section now lists what pauses a queue and how to continue.

Surfaces: the server rule applies to every client. Web and desktop share the header; mobile has its own banner. Providers are unaffected: the hold lives in orchestration, not in an adapter.

Scope and approval

Bug fix for #18805 (filed with the event log from the affected thread). Stop's hold is intended, but the hold had no exit when a person kept working. The fix gives sending the same effect as pressing Resume, and leaves the hold on every automatic path.

Verification

  • vp test run apps/server/src/orchestration-v2/ThreadStop.test.ts: 14/14 pass. Five tests are new: a client send after Stop releases the held queue; a scheduled prompt doesn't; a re-sent late steer doesn't; messages held behind a worktree that was never created stay held. I disabled each guard in turn and confirmed its test fails without it.
  • tsc passes for apps/server, apps/web and apps/mobile, and lint and format pass on the changed files.
  • Checked end to end in a real web client against an isolated dev server with a Codex thread. I started a sleep 90 turn, queued a message, and pressed Stop, and the queue showed Paused. Then I sent a new message. The header switched to Queued straight away. The new run completed, and the released message started 0.4 s later and completed, with no Resume.

Before (main): held after Stop, but shown as "Queued"

before

After: the same held queue, shown as "Paused"

after

The before shot uses the same held state, with the old label restored through hot reload.

Not checked: the mobile banner on a device (copy-only change, typechecked), and a Claude thread (the hold and release are provider-independent).

Models

  • Development: Claude Opus 5.5 in Claude Code via T3 Code traced the bug from the affected thread's event log, wrote the fix and tests, and ran the browser check.
  • Review: GPT-6.1 Sol in Codex CLI found that a send after switching provider skipped the release, and that releasing messages held behind a missing worktree would run them in the project checkout. Claude Opus 5.5 found that scheduled prompts and re-sent late steers carry a client source and would release the hold, and that the first banner copy was inaccurate in some states. All four were fixed before opening.

Stop holds the queue behind the run it interrupts. A message sent afterwards
started at once on the idle thread but left the held messages held, so they
never ran, and the web queue gave no sign they were paused.

A new message a person sends from web or mobile now releases the held queue,
so those messages follow it. Scheduled prompts, re-sent late steers, and
other automatic sends keep the hold, as do messages waiting on a worktree
that was never created. The web queue header and mobile queue sheet say when
the queue is paused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 12, 2026
Comment on lines +5225 to +5228
const worktreeMissing =
(latestRun?.status === "failed" || latestRun?.status === "interrupted") &&
latestRun.workspacePreparation?.type === "worktree" &&
projection.thread.worktreePath === null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High orchestration-v2/Orchestrator.ts:5225

After a failed worktree preparation and an intervening turn, this code releases all held runs even though thread.worktreePath is still null; those runs then execute in the project checkout instead of the intended worktree. worktreeMissing only checks latestRun, so the completed intervening run hides the failed preparation; preserve that hold while any failed or interrupted worktree preparation remains unresolved.

-      const worktreeMissing =
-        (latestRun?.status === "failed" || latestRun?.status === "interrupted") &&
-        latestRun.workspacePreparation?.type === "worktree" &&
+      const worktreeMissing =
+        projection.runs.some(
+          (run) =>
+            (run.status === "failed" || run.status === "interrupted") &&
+            run.workspacePreparation?.type === "worktree",
+        ) &&
         projection.thread.worktreePath === null;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration-v2/Orchestrator.ts around lines 5225-5228:

After a failed worktree preparation and an intervening turn, this code releases all held runs even though `thread.worktreePath` is still `null`; those runs then execute in the project checkout instead of the intended worktree. `worktreeMissing` only checks `latestRun`, so the completed intervening run hides the failed preparation; preserve that hold while any failed or interrupted worktree preparation remains unresolved.

@macroscopeapp

macroscopeapp Bot commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Would Approve

Macroscope's review found this PR approvable — This is a narrowly scoped queue-continuation bug fix with targeted regression coverage and presentation-only client/documentation updates. An unresolved High finding identifies a worktree-safety edge case in the new release guard, so that correctness risk remains blocking under the repository threshold.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Copy link
Copy Markdown
Member

Note

This comment is posted by Julius' dot

Closing because a new send now resumes previously held work after Stop, failure, or restart, changing the existing explicit Resume workflow. #18805 has no maintainer approval for that change, which the prior-approval rule requires. The misleading queue label is a useful bug report. Please agree on the resume behavior with maintainers first, or submit the paused-state visibility fix while preserving the current hold behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Queued message stays stuck after Stop when a new message is sent

2 participants