Skip to content

fix(claude-queue): priority-aware queue reconstruction — completed agents stuck as queued + queued prompt vanishing - #469

Merged
Juliusolsson05 merged 1 commit into
integration/rendering-pipelinefrom
fix/soak-queue-notification-bugs
Jul 7, 2026
Merged

Juliusolsson05 merged 1 commit into
integration/rendering-pipelinefrom
fix/soak-queue-notification-bugs

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

Root cause (one defect, both symptoms)

Two soak bundles from the same Claude session (render pipeline flag on) captured the same underlying bug:

  • debug-bundles/manual/2026-07-07T13-17-20-472-5b19529f — "A lot of problems, completed agents renders as queued.."
  • debug-bundles/manual/2026-07-07T13-17-48-452-5b19529f — "a queued prompt did not get shown in the feed. a lot of issues."

useIpcSubscriptions replays Claude's queue-operation transcript records into queuedMessages, but reconstructed each dequeue/remove as a blind slice(1) (drop the head). Claude's real queue is a PRIORITY queue, not FIFO (vendor/claude-code-src/full/utils/messageQueueManager.ts):

  • enqueue (user prompts) → priority 'next' (1)
  • enqueuePendingNotification (<task-notification>) → priority 'later' (2)
  • dequeue removes the lowest priority number, FIFO within a level, and logs no content/identity.

So a user prompt is delivered ahead of already-parked completed-agent notifications regardless of insertion order. Whenever the queue mixed priorities, the head-drop removed the wrong logical item and permanently desynced our reconstruction from Claude's provider-owned queue — which the idle-clear invariant deliberately never repairs (queueInvariants.ts, capability-gated to skip Claude). Both symptoms fall out of that single drift:

  1. completed agents render as queued — the two completed task-notifications get stuck in the QueueStrip; html-clean.html painted 2 queued with Agent "Audit structural conventions + isolation" finished completed — delivering to agent….
  2. queued prompt not shown — the user's own queued prompt vanishes from the QueueStrip, which is the only surface a Claude queued prompt appears on (claude has usesOptimisticUserEcho: false, so it never mints optimistic feed rows). The "agent-feed blending into main feed" hypothesis was disproven: QueueStrip renders unconditionally, outside the ledger, regardless of the pipeline flag.

Fix

  • claudeQueueReconstruction.applyClaudeQueueDequeue (new) mirrors Claude's priority dequeue by inferring priority from content (<task-notification> = 'later', else 'next'). It collapses to slice(1) for a homogeneous queue, so the common all-prompts path is byte-for-byte unchanged.
  • Wired into the dequeue/remove branch in useIpcSubscriptions.ts.
  • QueueStrip: stop appending the raw <status> after the summary — the summary already reads Agent "X" finished, so the append produced the finished completed … nonsense; the ✓/✗ glyph already carries the outcome.

Tests / gates

  • New unit suite claudeQueueReconstruction.test.ts (6 cases: mixed + homogeneous queues, the exact bundle shapes). Passes.
  • Full unit suite: 393 passed. (One unrelated pre-existing submodule suite agent-voice-dictation/.../hotkeyBinding.test.ts reports "no test suite found" — a node --test file, not mine.)
  • Extended the bundle corpus with the two new fixtures — both render at ledger parity (zero divergences), because the bug lives in the QueueStrip, outside the ledger diff the corpus asserts.
  • tsc -b tsconfig.node.json: clean (exit 0).
  • tsc --noEmit -p tsconfig.web.json (after node build, TS6305 ignored): the only error is pre-existing and unrelated — collectLedgerInput.ts(113): Property 'lookups' does not exist on type 'RuntimeSemanticTurn', introduced by the last-merged PR fix(rendering): collapsed-running tail gate — first live soak catch #465 (collapsed-running tail gate), confirmed present with my edits stashed. Left untouched to avoid conflicting with in-flight work; flagging for the owner.

🤖 Generated with Claude Code

…-head

Two soak bundles from one Claude session (render pipeline flag on) captured
the same root cause:
  - debug-bundles/manual/2026-07-07T13-17-20-472-5b19529f
    "completed agents renders as queued"
  - debug-bundles/manual/2026-07-07T13-17-48-452-5b19529f
    "a queued prompt did not get shown in the feed"

Root cause: useIpcSubscriptions replayed Claude's `queue-operation` transcript
records into `queuedMessages`, but reconstructed a dequeue/remove as a blind
`slice(1)` (drop the head). Claude's real queue
(vendor/claude-code-src/full/utils/messageQueueManager.ts) is a PRIORITY queue:
user prompts enqueue at 'next' (1), `<task-notification>` payloads at 'later'
(2), and dequeue removes the LOWEST priority number, FIFO within a level. So a
user prompt is delivered ahead of already-parked task-notifications regardless
of insertion order. When the queue mixes priorities, the head-drop removed the
wrong logical item, permanently desyncing our reconstruction from Claude's
provider-owned queue (which the idle-clear invariant deliberately never
repairs — queueInvariants.ts). Both symptoms fall out of that drift:
completed-agent notifications get stuck in the QueueStrip, and the user's own
queued prompt vanishes from it (the only surface a Claude queued prompt shows,
since claude has usesOptimisticUserEcho:false).

Fix:
- New claudeQueueReconstruction.applyClaudeQueueDequeue mirrors Claude's
  priority dequeue by inferring priority from content (task-notification =
  'later', else 'next'); it collapses to slice(1) for a homogeneous queue, so
  the common all-prompts path is unchanged.
- Wire it into the dequeue/remove branch in useIpcSubscriptions.
- QueueStrip: stop appending the raw <status> after the summary — Claude's
  summary already reads `Agent "X" finished`, so the append produced the
  "finished completed — delivering to agent…" nonsense the user screenshotted;
  the ✓/✗ glyph already carries the outcome.

Tests: unit coverage for the priority reconstruction (mixed + homogeneous
queues). Extended the bundle corpus with the two new fixtures (ledger feed is
at parity for both — the bug lived in the QueueStrip, outside the ledger diff).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant