Skip to content

bug(claude-queue): committed-entry identity matches notifications by task id alone #678

Closed

Description

@Juliusolsson05

Expected behavior

The identity pass — the step that turns a dequeue from a guess into proof — must retire the queued notification the committed user entry actually carries.

Actual behavior

entryClaims matches a queued task-notification on its correlation id alone:

if (!isTaskNotification(entry.text)) return false
const id = notificationIdOf(item.content)
return id !== null && entry.text.includes(id)

The docstring justifies this with "Notifications match on a correlation id (present on 1144/1144 in the current corpus)" — presence is universal, but uniqueness is not.

In testing/fixtures/queue-operations/divergence-stranded-background-commands.json, 2 of 126 enqueued notification task ids carry two different bodies. Upstream reuses the id when reconciling a background shell from a previous session, so the queue can hold both under one id:

  • Background command "Watch both workflows to completion" was stopped
  • No completion record was found for this background shell command from the previous session…

With both queued, state.pending.find(...) returns whichever was enqueued first. If the committed entry carries the second verbatim, the first is retired as delivered-observed using the second's UUID as evidence, and the item that actually departed stays pending.

That is the module's defining failure — the wrong item removed, with a convincing-looking cause — and the queue-length-based assertions the module warns about would bless it.

Reproduction

Replay divergence-stranded-background-commands.json events 111–114. The resulting decision's preview names the first notification while its evidence names the second's committed UUID, and the second remains pending. Duplicate ids occur at fixture lines 628 and 1973.

Scope

Pre-existing. entryClaims is byte-identical at 3223c51c, so this reproduces on main today and is not introduced by PR #670.

Filed separately rather than folded into #670 for two reasons. It is outside that PR's problem statement (#665 is about durable queued prompts never rendering), and more importantly the recorded corpus expectations were blessed while this misattribution was live — so correcting it changes recorded outcomes and needs its own evidence pass rather than a merge-gate patch.

Suggested direction

PR #670 fixes the same class of defect on the sibling remove-carrier path via resolveRemoveCarrierTarget: prefer exact normalized text, fall back to id only when unambiguous. entryClaims cannot reuse it directly, because a committed entry legitimately wraps the notification rather than equalling it (that is why prompts use prefix containment). The equivalent strengthening is to prefer the candidate whose full content is contained in the entry text, and fall back to id matching only when a single pending item carries that id.

Evidence

  • src/renderer/src/session-runtime/claudeQueue/reconcile.ts — entryClaims.
  • testing/fixtures/queue-operations/divergence-stranded-background-commands.json — the two duplicate-id pairs.
  • git show 3223c51c:...reconcile.ts — identical pre-PR definition.

Found by an orchestrated review of PR #670.

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

    class:C2-identityIdentity/ownership across reload, replace, restore, wakeprovider:claudeAffects Claude Code integrationsev:P3Minortype:bugSomething works wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions