Skip to content

fix(claude-queue): settle proven queue debt at bootstrap-complete (#677) - #1396

Draft
Juliusolsson05 wants to merge 6 commits into
mainfrom
fix/claude-queue-idle-settlement
Draft

Juliusolsson05 wants to merge 6 commits into
mainfrom
fix/claude-queue-idle-settlement

Conversation

@Juliusolsson05

@Juliusolsson05 Juliusolsson05 commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

HELD behind #1402 (manager q101). Refs #677.

The bootstrap guard debt >= pending can still retire a genuinely queued prompt when a dequeue is redelivered. The counterexample is committed as an it.fails test, 'does not retire a still-queued prompt at bootstrap when a dequeue is redelivered (#1402)'. Nothing observable at bootstrap distinguishes it from the recorded fixture, so this PR cannot merge until #1402 makes each departure countable once. Then the test flips to it and must pass.

Plan: docs/plans/2026-09-27-claude-queue-idle-settlement.md.

Problem

markStaleWhenIdle is how Claude queue debt settles and how unattributed survivors are marked stale. Its only live call site was a semantic event arriving while the session was already idle. Production bootstrap receives no replayed semantic events, so a replay that ended with open debt kept its chips indefinitely. The recorded exact-remove-after-open-dequeue-debt run ends with 3 pending items and debt.count = 3.

What merges

  • Bootstrap-complete settles the Claude queue through a new helper, settleClaudeQueueIfIdle, but only when (a) there is no live signal (!processActive && streamPhase === 'idle') AND (b) the open departure debt (debt.count + removeDebt.count) is at least the number of pending items, stale ones included (debtCoversPending). Each debt unit is a departure Claude logged whose item was not identified, so every pending item has left and nothing is left to stale-mark. The result is committed to claudeQueueBySession after setRuntimes returns.
  • The semantic site is refactored onto the same helper with its existing guard and behaviour unchanged.
  • Not the process-idle flip (manager decision, option B). Review round 2 found that a REDELIVERED dequeue double-counts debt. Queue-operation records have no uuid, and the local corpus has genuine same-millisecond repeats, so there is no key-based guard. At a live flip, a phantom unit could hide a genuinely queued user prompt. That half of bug(claude-queue): idle debt settlement is only reachable from a semantic event #677 is blocked on bug(claude-queue): a redelivered dequeue/remove double-counts queue debt #1402.

Review history

  • Round 1 (a): idleness is not proof the queue drained, so a genuinely queued N2 went stale → the debt-coverage proof was added.
  • Round 2 (a):
    1. redelivery double-counts debt → the process-idle site was dropped, and the residual is filed as bug(claude-queue): a redelivered dequeue/remove double-counts queue debt #1402;
    2. stale items were missing from the coverage count → they now count.
  • The review cap applies: B6 inspects the final head by hand.

Tests

claudeQueueIdleSettlement.renderer.test.tsx drives the real useIpcSubscriptions through the fake session feed:

  • the recorded fixture replay settles at bootstrap-complete and stays settled (a later user entry must not bring an item back);
  • no bootstrap settlement while the process is live or the stream is still responding;
  • a genuinely queued N2 (N1 delivered, no debt for N2) stays live, both at bootstrap and after a process-idle flip;
  • a stale item counts against the debt, so one genuine departure of a stale S does not settle a live N;
  • removed site: with the issue's exact carrier sequence, a process-idle flip does NOT settle.

Fail-first and mutations, each killed:

  • the bootstrap settlement fails on origin/main;
  • the removed-site test fails with the process site reinstated (85f7ee0c);
  • no debt proof at bootstrap;
  • live-only coverage count;
  • no bootstrap commit;
  • bootstrap ignoring the live signal or the stream phase.

Blocker: the redelivery double-count (#1402) also retires a queued prompt at bootstrap (pinned it.fails). It still affects the existing semantic site and committed-user inference, as it does on main.

Gates: npx tsc -b + src/renderer/src/workspace/hook/ipc + src/renderer/src/session-runtime/claudeQueue on 543e1412 (14 files, 145 tests).

🤖 Generated with Claude Code

Juliusolsson05 and others added 5 commits September 27, 2026 02:46
)

markStaleWhenIdle was reachable only from a semantic event that arrived
while already idle. The process-idle flip and bootstrap-complete now
settle too, through one helper, and commit the result to the reconciler
state after setRuntimes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ure from disk (#677)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n debt covers every live item (#1396 review a)

Idleness is not proof the queue drained: between turns, or on a resumed
pane's first replay-quiet tick, a genuinely queued item was marked stale.
The two new sites now settle only when logged departures account for every
live pending item. The semantic site keeps its existing guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…livery residual (#1396 review a round 2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-idle site (#1396, manager option B)

A redelivered dequeue can make the debt cover a genuinely queued prompt;
at a live process-idle flip that hides a real queued user prompt. The
process-state handler is back to main's; a removed-site test pins it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05 Juliusolsson05 changed the title fix(claude-queue): settle queue debt on every transition into idle (#677) fix(claude-queue): settle proven queue debt at bootstrap-complete (#677) Sep 27, 2026
…ple as a known failure (#1396 parked behind #1402, q101)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Juliusolsson05 Juliusolsson05 added the type:bug Something works wrong label Sep 27, 2026
@Juliusolsson05

Copy link
Copy Markdown
Owner Author

Review disposition (head cb17181). HELD under q101, parked behind #1402

Status: this PR does not merge in its current form. debt >= pending cannot tell a genuine departure from a redelivered dequeue, so no settlement site may retire a queued prompt on that evidence.

Round Finding Disposition
a r1 settling at process-idle can retire a genuinely queued prompt Accepted. B6 chose option B: the process-idle site was removed
a r2, finding 1 debt >= live pending does not prove that THOSE items departed: dequeue debt grows whenever any item is pending, so a redelivered dequeue (N1 delivered, N2 still queued) makes debt cover N2 Accepted. Rerun at the remaining bootstrap-complete site (q101): it retires N2 there too. Committed as an it.fails pin at cb17181 (test-only), so the case is recorded and flips to it once fixed
a r2, finding 2 stale items are left out of the coverage count but are still inference candidates (the "count all pending" mutation survived) Accepted. It needs the same departure-identity fix

Why parked, not narrowed further: nothing observable at bootstrap distinguishes the recorded fixture from redelivery. Queue-operation records carry no uuid (corpus: 606 transcripts, 2,391 dequeues, 99 genuine same-millisecond repeats). #1402 proposes counting departures once (retire only items enqueued before the current process started, a provable departure). If B6 accepts that direction, this PR closes in its favour, or is rebuilt on top of it.

Never: retire a real queued prompt to make the chip state tidier (B6, option B).

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

Labels

type:bug Something works wrong

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant