Read what a Claude Code notification means before calling it 'needs you' (#306) - #310
Merged
Merged
Conversation
…ou' (#306) The Notification hook reported presence=awaitingInput for every notification without reading its payload. Claude Code fires one about sixty seconds after a turn ends — idle_prompt, meaning still idle — so every resting loop flipped to 'needs a human' a minute after it stopped, and since staged follow-ups and Mailroom wakes deliver only on idle, they were parked for good: 0 of 18 watched posts arrived. A notification.sh reporter now reads notification_type: idle_prompt confirms idle; permission_prompt and elicitation_dialog stay awaitingInput; auth_success leaves presence alone; an unknown or absent kind keeps today's behaviour, because a loop that genuinely needs someone becoming invisible is the worse failure. The hook falls back to today's report when the script is unreadable, and remote hosts are handed the script. Every NEEDS-YOU surface had been over-reporting for any loop idle past a minute. Closes #306. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DeGL2CxuGmq16RSZpJYm2N
scgopi
force-pushed
the
fix/306-idle-prompt
branch
from
September 7, 2026 00:57
ea9af81 to
d956d34
Compare
Owner
Author
|
Gate on d956d34, private DerivedData: The one amendment since opening: the end-to-end test attached a bare |
scgopi
added a commit
that referenced
this pull request
Sep 7, 2026
Drain staged follow-ups once, in order (#304) `drainPendingFollowUps` awaited the actor per item, so two drains interleaved over one snapshot and the final `pendingFollowUps = remaining` was a lost update. That single defect produced three symptoms that looked like separate bugs: duplicates, reordering, and — the one nobody saw, because it is invisible — messages dropped entirely. An end-to-end loop measured 5 of 18 posts never arriving and #357 delivered before #347. The drain now takes and clears in one actor step, folds retries back in front of anything queued meanwhile, and refuses to re-enter. Reproduced before the fix: three staged messages delivered three times each, out of order, and a wake sent for a post the reader had already read. Reviewed independently. The reviewer wrote five of its own tests and ran them against both this head and main's GraphStore in the same worktree, so its findings are measured before/after rather than read. Two follow-ups it raised, neither blocking and both filed separately: a later follow-up can jump the queue when the cached presence the poll wrote disagrees with the live reading the drain takes, and the presence read has no deadline — an await that never returns holds the guard forever. It checked whether the guard makes that worse than main and it does not; main re-enters into the same hang and re-delivers everything before the stuck item as well. Gated on the merged result together with #310, since the two fix the same user-visible failure from opposite ends: 1662 tests / 175 suites / 0 failures, no restarts, CLI smoke green. Closes #304.
This was referenced Sep 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
graphcode's
Notificationhook for Claude Code reportedpresence=awaitingInputfor every notification, without reading the payload. Claude Code fires a notification about sixty seconds after a turn ends —notification_type: idle_prompt, meaning still idle — so every resting loop flipped to "needs a human" a minute after it stopped, and stayed there. Staged--follow-upmessages and Mailroom watch wakes deliver only onpresence == idle(deliversLater,drainPendingFollowUps), so for any Claude Code loop idle over a minute — most loops, most of the time — they were parked for good. MailWatcher measured it: two loops flipping at 60.0 s and 60.1 s, 0 of 18 watched posts typed in, all 18 staged.The change
A
notification.shreporter besideactivity.shandusage.sh— the Notification hook is the one whose meaning depends on its payload. It readsnotification_typeand maps:idle_promptidle(activity cleared)permission_prompt,elicitation_dialogawaitingInputauth_successawaitingInputThe hook command runs the reporter and, if the script is unreadable, falls back to today's report (
awaitingInput), never to nothing. Remote hosts are handed the reporter beside the settings that name it, as with the other two scripts.Audit of what keys off
awaitingInput: the drain anddeliversLater(the bug); and every NEEDS-YOU surface — the sidebar pill, the canvas attention rail, the titlebar indicator,AttentionRollup— which has therefore been over-reporting for every loop idle past a minute: the second symptom, now gone with the first.MessageBus.deliverabilitykeys onLoopState.awaitingInput(a turn-based check), not on presence, and is unaffected — which is why a plainnode sendstill arrived while--follow-updid not. The OpenCode plugin'squestiontool and Copilot's log reader map genuine prompts only and are untouched.Verification
Gate: full Xcode suite (gate on this head in flight; numbers and exit codes in a comment when it completes), swiftlint 0 errors, swift-format clean,
graphcodedandgraphcode-clischemes build; SwiftPM build and CLI smoke pass; Linux green.NotificationPresenceTestsruns the real generated reporter under/bin/shwith the payload on stdin against azmxthat records what it was asked to set — the shapePresenceReportingTestsestablished, because the risk is asedsurviving Swift, JSON and shell:idle_prompt→presence=idlewith the activity cleared;permission_promptandelicitation_dialog→awaitingInput; an unknown kind and an absent field →awaitingInput;auth_success→ nothing set; no$ZMX_SESSION→ nothing set; the hook JSON runs the reporter and falls back toawaitingInput; the remote fragment carries the script. And the end-to-end path MailWatcher measured: a store with a watch armed, a matching post and a--follow-upstaged for a busy loop delivers nothing on the presence the reporter writes for a permission prompt, and both on the presence it writes foridle_prompt.Closes #306. Related: #304 (what the drain then did wrong to the backlog once it ran).
🤖 Generated with Claude Code
https://claude.ai/code/session_01DeGL2CxuGmq16RSZpJYm2N