TaskBeacon vs. the official notification hook
Search for "how do I get notified when Claude Code finishes" and almost every
answer points at the same thing: add a Notification hook to
~/.claude/settings.json. That is a reasonable floor, and a lot of menu
bar tools are built directly on top of it.
This page is about where that floor has holes — each one with an issue number you can open yourself — what a hook fundamentally cannot see no matter how well it is written, and what happened when we actually measured the alternatives. Where TaskBeacon is the weaker option is in section 5, not buried in a footnote.
1. Four documented failure modes of the Notification hook
All four are filed in anthropics/claude-code and were re-checked on 2026-08-03. Three of the four are closed — this is not a claim that the hook is broken today. It is a claim that these four classes of failure are on record, and that a monitor which has only this one signal has no answer when any of them happens.
| Failure mode | Issue | Status |
|---|---|---|
| Alert fatigue. idle_prompt fires after every response, so "your turn" and "waiting on your approval" are indistinguishable | #12048 | Closed as duplicate |
| Silent inside the VS Code extension. The same settings.json fires normally in the CLI and produces no events in the native extension | #59718 | Closed as duplicate |
| Permission events lost during thinking. The prompt is visible in the TUI while the hook never fires | #58909 | Closed (version regression, since fixed) |
| Multi-second delay between the event and the hook actually being invoked | #19627 | Open |
What TaskBeacon does instead
- Alert fatigue — "done" and "needs you" arrive on two separate hook events (Stop and PermissionRequest). The ambiguous idle_prompt is never allowed to write either state; it is only used as an interruption-recovery signal.
- Lost permission events — the red state comes from PermissionRequest and from PreToolUse matching the interactive tools, so it is not behind the idle gate that dropped events during thinking. The daemon's own session state is a second, hook-independent fallback.
- Delay — a session turns red the moment the tool call that will ask you something is seen, rather than waiting for a delayed idle notification.
- Coverage — sessions are discovered by walking live processes, so a session exists in the list whether or not any hook has ever fired for it.
2. The blind spots no hook can see
These are not bugs in anyone's implementation. They are places where Claude Code emits no event at all, so a purely hook-driven monitor is structurally blind and a transcript-driven one is guessing from file timestamps.
The approval blind spot
You click approve on a permission prompt. No hook fires at that moment. The next event, PostToolUse, arrives only when the command finishes. So between your click and the end of a long command, a naive hook-driven monitor keeps showing "awaiting permission".
We reproduced this against claude-status-bar's own shipped hook code, replaying a real event sequence in an isolated environment:
Its permission state is released by PostToolUse, a 7200-second cap, or an "interrupted by user" line in the transcript (Sources/main.swift:1207-1216). In practice: a foreground command shows "awaiting permission" for as long as it runs.
TaskBeacon closes this by probing the terminal's process tree — if real work is running under that tty, the session is working, not blocked — with a timestamp guard so that a new prompt raised while a leftover shell is still alive is not swallowed by the same probe.
A background subagent asking for permission
When a background subagent hits a permission prompt, the request does not reach the main terminal's hooks — the main turn has already emitted Stop. TaskBeacon keeps a per-terminal ledger of background agents and fills this case in from the daemon's session state, so the row goes red even though nothing was delivered to that tty.
Background shells still running
When a run_in_background command is still going after the main turn ends, the session is available to you — so it is correctly "done", not "working". The useful thing is not a different colour, it is saying so: TaskBeacon shows the count of background shells still running on that row.
3. What we measured
Two evidence types were required for anything quoted here: a citable line of source and a reproduction on a real machine. Single-evidence findings exist in our working notes but are deliberately not quoted on this site.
Sample. One machine, a five-minute window of real work sampled every 5 seconds: 58 sample points, 348 observations. The reference was the status field Claude Code itself maintains in ~/.claude/sessions/<pid>.json.
| Reference state | Observations | c9watch correct | TaskBeacon correct |
|---|---|---|---|
| waiting — genuinely blocked on you | 18 | 0 / 18 | 18 / 18 |
| busy — the model is working | 161 | 124 / 161 | 161 / 161 |
The cliff lands exactly on its own threshold
Breaking c9watch's accuracy on busy sessions down by how old the transcript file was at that moment:
| Transcript age | Correct | Reported instead |
|---|---|---|
| 0–10 s | 52 / 57 — 91% | Connecting ×5 |
| 10–20 s | 40 / 41 — 98% | Connecting ×1 |
| 20–30 s | 30 / 32 — 94% | Connecting ×2 |
| > 30 s | 0 / 27 — 0% | WaitingForInput ×26, Connecting ×1 |
The drop lands precisely on the 30-second freshness threshold in its own source (src-tauri/src/session/status.rs:80). A monitor that infers liveness from file mtime goes blind exactly when the transcript goes quiet — and a session sitting on a permission prompt is a session whose transcript is not moving. In our runs, sessions parked on a prompt had transcripts untouched for 591 and 668 seconds.
Two more results from the same run:
- 0 of 18 on the case that matters most. Across 18 observations of sessions genuinely blocked waiting on the user, c9watch reported "needs attention" not once — 16 times "waiting for input", twice "working". The user sees idle, not urgent.
- One live session missed at all 58 sample points. Its transcript had been still for 1147 seconds. This matches its own open issues #46 and #76.
Method, and why it did not touch anything
Nothing was installed into the shared configuration to produce these numbers. Two zero-pollution paths were used instead: claude-status-bar's own shipped hook code was replayed under an isolated HOME with payloads captured from a real machine, and c9watch was exercised through its official signed pre-built CLI in read-only mode. The global ~/.claude/settings.json was never modified.
4. Where TaskBeacon is the weaker choice
Every claim above has a boundary. These are ours.
- No terminal, no row — and no warning. TaskBeacon keys sessions by controlling tty. Its status hook walks up the process chain looking for a terminal ancestor and exits early if it finds none, before it has written anything at all. In a host that never allocates a pty, a session does not appear in the list. It is not a wrong colour you might notice — it is an absence you will not. Everything on this page assumes terminal sessions. Monitors that read the transcript are immune to this by construction, because they never look at the process chain.
- It needs its hooks installed. c9watch requires zero integration. Its lag is the price of that design, and our accuracy is bought with a setup step. Quoting one without the other would not be an honest comparison.
- The sample is small and single-machine. Five minutes, one Mac, real workload rather than exhaustive scenarios. Treat the numbers as a reproducible observation, not a universal benchmark. They will be re-run on a longer window with controlled scenarios, and refreshed each quarter — a version bump on either side can invalidate any of them.
- No circular scoring. The daemon state used as the reference is also one of TaskBeacon's inputs. In all 18 blocked-session observations, TaskBeacon's raw hook state was already needs independently, so the 18 of 18 is not the reference grading itself. It is still worth knowing.
- c9watch's approval handling is conditionally fine, not broken. It simulates the auto-approval decision, so allowlisted commands are correctly shown as working. Its weakness is the freshness cliff, not the approval moment. We downgraded our own earlier assessment of it after measuring.
- Sessions that fire no events are not free. Hooks only affect sessions opened after installation, or still active at that point. A terminal that has fired nothing since install has no state file yet.
- Some competitor behaviour was not black-box verified. claude-status-bar's GUI rendering and claude-status's primary state path (its plugin is a private submodule) were assessed from source and replay only, and are therefore not quoted as measured results anywhere on this site.
5. Sources
- Claude Code issues #12048, #59718, #58909, #19627
- c9watch v0.8.1 — src-tauri/src/session/status.rs:80-96, 264-273
- claude-status-bar — Sources/main.swift:1207-1216, hooks/update.js
- claude-status — minimum macOS 26.2; TaskBeacon's is 13.0 (build.sh:19, LSMinimumSystemVersion)
- TaskBeacon itself is closed source, so the competitor claims above are the ones you can independently re-run — every one of them cites a public file and line, and both tools ship binaries you can point at your own machine. Our side of the table is reproducible in the other direction: install TaskBeacon, park a session on a permission prompt, and watch whether the row goes red and stays red.
Numbers on this page are re-measured each quarter, and whenever either tool ships a release that touches its status logic. A version bump on any side can invalidate a row — the versions each figure was taken against are printed at the top of this page for exactly that reason.