cd /news/developer-tools/the-notification-that-lied-to-me Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-55547] src=dev.to β†— pub= topic=developer-tools verified=true sentiment=Β· neutral

The Notification That Lied to Me

A developer built claude-code-notify, a Telegram notification tool for Claude Code sessions, after experiencing productivity losses from long TDD-driven tasks and parallel project management. The tool uses transcript-based matching on tool_use_id to accurately detect when background tasks complete, avoiding false positives from immediate acknowledgments. The developer also found that Claude Code's native SubagentStop hook is unreliable for background agents, making transcript parsing the only reliable signal.

read6 min views1 publishedJul 11, 2026

I was three projects deep. Project A had a review open in one VS Code window. Project B was running under Claude Code with the superpowers

skill set, which means TDD: red, green, refactor, repeat β€” a mode that produces solid code but stretches a task from minutes into an hour or more. Somewhere in that hour, Claude hit a point where it needed my sign-off to keep going. I didn't notice. I was in Project A. By the time I switched back to Project B, it had been sitting there, blocked, for the better part of twenty minutes β€” doing nothing, waiting on me.

That's the first problem: long, TDD-driven tasks create real decision points, and if you're not watching, you bleed time waiting for nobody.

The second problem is the same failure wearing a different hat. I run multiple projects in parallel, each in its own VS Code window, each with its own Claude Code session. "Is anything done? Is anything stuck?" isn't a question I can answer without clicking through every window and every session, one at a time. Multiply that by a normal afternoon and it's a meaningful amount of dead time spent just checking.

Both problems have the same shape: Claude Code knows when it's blocked or finished. I don't, because I'm not watching. So I built claude-code-notify

β€” a tool that pings me on Telegram at exactly the moments that matter: when a turn is truly done, and when it needs me.

The obvious fix is a Stop

hook: Claude Code exposes one, you wire it to a script, the script fires a Telegram message. I did exactly that, first. It worked β€” until it didn't.

The bug showed up as a notification that arrived too early. I'd get "finished," switch over, and find the terminal still working. The cause, once I found it, was almost embarrassing: Claude Code's Bash

tool, when run with run_in_background=true

, immediately returns an acknowledgment β€” something like "Command running in background with ID: …"

β€” the instant the command is dispatched, not when it completes. A naive Stop

hook that treats any matching tool result as "resolved" reads that ack as done. It isn't. The command is still running. Background Agent

subagents have the same issue from a different angle: they don't emit an ack at all, so a hook that only tracks explicit "finished" signals just never sees them.

So the naive hook was worse than useless for exactly the cases I cared about most β€” the long, multi-step, background-heavy turns that made me build this in the first place.

The fix is a single rule: a background task β€” a subagent or a backgrounded Bash

command β€” counts as resolved only when a <task-notification>

shows up in the session transcript carrying that task's original tool_use_id

. An immediate acknowledgment never counts. At Stop

time, the hook computes pending = launched βˆ’ resolved

; if anything is still pending, it stays silent. Only once every background task has actually reported back does it check a rate limit and send.

I'd expected to just use Claude Code's native SubagentStop

hook for this and skip the transcript parsing entirely. It turns out that hook can't do the job: background agents (run_in_background=true

) bypass Stop

/SubagentStop

entirely, subagent completion isn't reliably reported through it, and even when it does fire, it carries the parent session's shared session_id

with no way to tell which subagent finished. All three are open issues against Claude Code itself, not quirks of my setup. Transcript-based matching on tool_use_id

is the only signal that's actually reliable today.

I added the error hook almost as an afterthought β€” a turn can die for any reason, and a silent dead turn is the same lie as an early "finished." Then one afternoon it turned out to be the most useful of the three.

I had three sessions running: two long autonomous turns and a review. Somewhere in there my account hit its usage limit. A usage limit doesn't stop one turn β€” it stops all of them, at once, mid-task, in work I couldn't see. On the old workflow I'd have found out the way I always did: clicking into each window and hitting a wall of red where I'd expected progress.

Instead my phone buzzed three times:

Claude Code stopped with error | Refactor the transcript parser | ~/work/foo | 11/07/2026 10:29:28

β€” and two more like it. Each ping named the project it came from and the turn's own title, because the error notification carries the same identifying fields as every other one: Claude Code's title for the turn, the working directory, and the time.

That's exactly the information a limit erases from your head. When it resets a few hours later, the question isn't "did something break" β€” everything broke β€” it's "where was I, in each project, when it did." The three messages sitting in my Telegram history answered that without my opening a single window: which projects, which tasks β€” enough to walk straight back into each one instead of reconstructing from scratch where I'd left off. The parallel-checking problem from the top of this post, in its worst form β€” several stalled sessions at once β€” collapses into a scrollback I can read on my phone while I wait for the limit to lift.

I kick off a long task, switch to another project, and stop thinking about it. One message arrives β€” on Telegram, so it reaches me regardless of which window has focus β€” and only for one of three reasons: the turn is genuinely finished, background work and all; it needs my input; or it errored out. No early "finished" while something's still churning, no manually checking five sessions to find the one that's stuck.

Both of my original problems turn out to be the same problem, and this is the same fix: stop relying on me to notice, and make the notification itself trustworthy.

curl -fsSL https://raw.githubusercontent.com/Jeromefromcn/claude-code-notify/main/install.sh | bash

One dependency (python3

), and config lives outside settings.json

so your token never gets committed by accident. Changed your mind? The same line with --uninstall

pulls the hooks and code back out (it leaves your config.env

alone, so reinstalling later doesn't mean re-entering your token):

curl -fsSL https://raw.githubusercontent.com/Jeromefromcn/claude-code-notify/main/install.sh | bash -s -- --uninstall

It's MIT-licensed: github.com/Jeromefromcn/claude-code-notify.

If you run Claude Code across parallel sessions or lean on long autonomous turns, I'd genuinely like to know if this solves the same problem for you β€” or if you hit a case it doesn't handle.

── more in #developer-tools 4 stories Β· sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/the-notification-tha…] indexed:0 read:6min 2026-07-11 Β· β€”