Skip to content

Snooze fails with "has a queued run" when a held queue never drains after restart #15862

Closed

Description

@kr4chinin

What happened

I tried to snooze a thread that looked idle and got this toast:

Failed to snooze thread
Thread <id> has a queued run and cannot be snoozed.

Nothing in the thread looked queued or running. The last run had completed and the thread showed as idle.

Why this happens (from the local state DB)

The thread's runs in orchestration_v2_projection_runs looked like this:

ordinal status requested_at (UTC) note
45 cancelled 02:17:25 cancelled at 02:46:29, when the app restarted for an update
46 queued 02:45:02 PR-watch notification (userMessageId: message:pr-watch:…), payload has queueHeld: true, queuePosition: 1
47 completed 02:50:07 my next manual message

Here's my reading of the code on main (1e2ecbd975):

  1. A PR-watch update was queued (run 46) while run 45 was active.
  2. The app restarted. ProviderRuntimeRecoveryService cancelled run 45 and set queueHeld: true on run 46 ("require explicit consent before draining them").
  3. When I sent a new message, run 47 started right away and completed. Run 46 stayed held. startNextQueuedRun returns early whenever any queued run has queueHeld === true, so nothing drains it without an explicit queue.resume.
  4. The thread.snooze guard in Orchestrator.ts rejects on runs.some(run => run.status === "queued"). It doesn't check whether the queue is held, so the thread can't be snoozed until someone resumes or cancels the held run.

The same restart left a second thread in the same state: a held PR-watch run at ordinal 23, while run 24 was running.

Expected

Any of these would fix it:

  • Snooze works on threads whose only queued runs are held. Held runs wait for consent anyway, so they don't need to block a snooze.
  • If held runs should block snooze, the error says the queue is paused after a restart, and the UI offers to resume or discard the held messages.
  • Sending a new message to a thread with a held queue either asks about the held items or makes them clearly visible, so a held run doesn't sit behind newer completed runs.

Question

Is it intended that the queue stays held after a new manual message has already started and finished a run? And should held queued runs block thread.snooze?

Environment

  • T3 Code (Nightly) 0.0.46-nightly.20261004.2657, macOS (Apple Silicon)
  • Provider: Codex

Activity

  1. juliusmarminge commented on Oct 5, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks @kr4chinin for digging into the local state and laying out the run table. That made it easy to trace. This reproduces from the code on main (1e2ecbd975), and the toast matches the thread.snooze rejection: Thread <id> has a queued run and cannot be snoozed.

    What I found

    On restart, queued runs that haven't started are marked queueHeld so they aren't sent without consent. A new message is only queued behind a run that is preparing, starting, running, or waiting, and a held run is none of those, so your manual message (run 47) started and completed on its own while the PR-watch run stayed held. Draining it still takes an explicit resume (empty-composer Resume on web and desktop, Resume queue on mobile).

    The held run here is a PR-watch notification, not a user message. The queue UI hides notification and delegated-completion runs, and the thread shell skips held runs when deciding what looks current, which is why the thread appears idle. Settle already treats those automatic queued runs as non-blocking and cancels them, but the snooze guard (runs.some(run => run.status === "queued") in Orchestrator.ts) has no such exception, so the hidden run blocks snooze.

    User-authored held messages are different: they stay visible in the queue and wait for resume.

    Likely fix area

    • One option is letting snooze ignore held automatic runs (notifications, delegated completions), matching how settle treats them.
    • Another is making the snooze error say the queue is held after a restart and offering resume or discard, which would also cover user-authored held messages.
    • Making held items more visible when a new message is sent, as you suggested, is a third direction.

    Workaround for now: settle the thread, which cancels the hidden PR-watch run, then unsettle it if you want it in the active list and snooze. Resume would deliver the held notification as a turn instead of dropping it.

    A maintainer will decide on the fix direction.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 5, 2026
  3. topitopongsala commented on Oct 10, 2026

    @topitopongsala

    Another reproduction, on 0.0.46-nightly.20261010.2908 (macOS desktop, canonical ~/.t3). Here it also causes chats that orchestrate children to stall for hours.

    After the app restarted at 2026-10-10 14:20Z:

    • 39 of the 41 queued runs in orchestration_v2_projection_runs carried queueHeld: true. They sat in 7 threads across 4 projects, and the oldest was from 2026-10-08.
    • Most of them are delegated-task completion notices ("Delegated task … reached a terminal state"). The parent never learns that its child finished, so it looks stalled and waits on helpers that already ended.
    • The operator's own follow-ups queue behind the held items, and they are never delivered either. In one thread, three queued instructions from 13:02Z to 14:14Z stayed held through a later cancelled run and a failed run, until they were cancelled by hand at 14:35Z.
    • New sends to an idle thread start at once and skip the held items: one thread ran ordinals 16, 17, and 18 while ordinals 4, 7, and 13 stayed held.

    The only recovery through the MCP tools today is t3_queue_read, then t3_queue_cancel, then sending the text again. There is no queue-resume tool. Two things would remove the stall:

    • a queue.resume in the UI and in MCP;
    • auto-drain on the first operator send after recovery.
  4. smagnetize commented on Oct 11, 2026

    @smagnetize

    Another reproduction on t3code-nightly-bin 0.0.46_nightly.20261010.2935 (Arch Linux desktop, Claude Code via the Agent SDK). It adds two things to the 2026-10-10 comment:

    • PR-watch keeps adding to a held queue after the work is done. One thread finished its last PR on 2026-10-09 and has been idle since. It still holds 88 queued runs, all queueHeld: true: 69 PR-watch updates and 14 scheduled-task wakes, the oldest from 2026-10-07. If someone pressed Resume, the thread would wake about 88 times on stale PR states. Across the install, 288 held runs sit in 13 threads.
    • Duplicate PR-watch notices. Two held runs on another thread carry the same text, "All 1 required check passed on 109bb50", for the same PR about 10 minutes apart (runs 293 and 296, 2026-10-11 06:12Z and 06:23Z).

    A held queue would be much less risky if PR-watch dropped an update for a PR that is already merged, and replaced an older held update for the same PR instead of adding another.

  5. naxey commented on Oct 11, 2026

    @naxey

    Note

    Claude Opus 5.5 responding on behalf of @naxey.

    Another reproduction of the held follow-up stall described above, on 0.0.46-nightly.20261009.2886 (aa8c6662), affecting operator messages and MCP t3_thread_send follow-ups between orchestrating agents. It adds the mechanism and scale.

    What I saw. Messages queued behind a running turn stopped being delivered, and agents that orchestrate other threads stalled because their follow-ups never arrived. Messages to an idle thread still started normally, so it looked random. I remember queued follow-ups working when I installed T3 Code on 2026-10-06; I first noticed this stall after updating to the 2026-10-09 nightly. This is my observed timeline, not a verified version regression.

    Mechanism. A restart hold spreads to every message queued afterwards. apps/server/src/orchestration-v2/Orchestrator.ts:5011-5016 on main @ ed0f7b90:

    ...(worktreeMissingRun !== undefined ||
    projection.runs.some(
      (candidate) => candidate.status === "queued" && candidate.queueHeld === true,
    )
      ? { queueHeld: true }
      : {}),

    The held-queue inheritance clause is also present in the installed nightly and was introduced in #2829; the worktreeMissingRun condition came later in #17654. A new queued message is created with queueHeld: true while any held queued run remains, and it is not sent automatically when the turn ends. Resume releases the queue; cancelling all held entries also removes the block. Idle sends are not blocked by held runs (isBlockingRun), so the thread keeps working while queued follow-ups pile up behind the old hold.

    Nothing tells the sender:

    • In the installed desktop/web source, held user messages show as ordinary "Queued" rows with no paused-state label. The composer offers Resume when its draft is empty. Open PR feat(web): resume a paused queue from the queue itself #14617 adds "Queue paused" and a "Resume queue" action. I did not inspect the actual desktop rendering.
    • t3_thread_send reports delivery: "queued" but not that the run is held. MCP queue list/read results also omit the held flag, and the MCP queue tools have no resume operation.

    Evidence. These are every transition to queueHeld: true from a previous value other than true, including runs created already held. Source: orchestration_events where application_event_version = 2, history through 2026-10-11 14:21:47Z.

    cause                                      holds  threads  event type   when (UTC)
    provider-runtime.reconcile                   8       3     run.updated  7 at 10-09 20:49:28; 1 at 21:53:31
    no command_id; worktree-continuation runs     3       3     run.updated  10-06 18:00:22–18:00:37
    message.dispatch                            40       2     run.created  10-09 21:01:13 → 10-11 14:21:47
    other identified receipt command types       0       0     —            —
    

    No hold transition joins a thread.stop or run.interrupt receipt. The three transitions without a command ID can't be classified from receipts.

    One Claude thread: run 211 was held by the reconcile at 20:49:28. Afterwards, 16 new queued runs (259 … 334) were created held. State after run 330 completed at 2026-10-11 14:03:26Z:

    ordinal  status     requested_at (UTC)  queueHeld
    211      queued     10-09 20:40         true   (restart hold)
    327      completed  10-11 13:59:14      -      (idle send, started immediately)
    328      queued     10-11 14:00:03      true   (queued behind 327; inherited the hold; still unstarted)
    329      completed  10-11 14:01:37      -      (idle send, skipped 328)
    330      completed  10-11 14:03:03      -
    

    Workaround used. Re-sent selected messages by hand; the original held entries stayed in the queue.

    Environment: NixOS, Electron 44.3, Node 24.20; Claude Agent SDK (Opus 5.5) and Codex (GPT-6.1-Sol). Database read read-only; source checked against the installed source map and main. Related PRs: #14617, #17885, #15201, #16926.

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