<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jerome</title>
    <description>The latest articles on DEV Community by Jerome (@jeromefromhk).</description>
    <link>https://dev.to/jeromefromhk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4024992%2F863898dc-f71f-4d89-b3d2-bf82893e303b.jpg</url>
      <title>DEV Community: Jerome</title>
      <link>https://dev.to/jeromefromhk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jeromefromhk"/>
    <language>en</language>
    <item>
      <title>The Notification That Lied to Me</title>
      <dc:creator>Jerome</dc:creator>
      <pubDate>Sat, 11 Jul 2026 14:19:37 +0000</pubDate>
      <link>https://dev.to/jeromefromhk/the-notification-that-lied-to-me-46lm</link>
      <guid>https://dev.to/jeromefromhk/the-notification-that-lied-to-me-46lm</guid>
      <description>&lt;p&gt;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 &lt;code&gt;superpowers&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;claude-code-notify&lt;/code&gt; — a tool that pings me on Telegram at exactly the moments that matter: when a turn is truly done, and when it needs me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive version, and why it lied
&lt;/h2&gt;

&lt;p&gt;The obvious fix is a &lt;code&gt;Stop&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;Bash&lt;/code&gt; tool, when run with &lt;code&gt;run_in_background=true&lt;/code&gt;, immediately returns an acknowledgment — something like &lt;code&gt;"Command running in background with ID: …"&lt;/code&gt; — the instant the command is &lt;em&gt;dispatched&lt;/em&gt;, not when it &lt;em&gt;completes&lt;/em&gt;. A naive &lt;code&gt;Stop&lt;/code&gt; hook that treats any matching tool result as "resolved" reads that ack as done. It isn't. The command is still running. Background &lt;code&gt;Agent&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "finished" actually has to mean
&lt;/h2&gt;

&lt;p&gt;The fix is a single rule: a background task — a subagent or a backgrounded &lt;code&gt;Bash&lt;/code&gt; command — counts as resolved only when a &lt;code&gt;&amp;lt;task-notification&amp;gt;&lt;/code&gt; shows up in the session transcript carrying that task's original &lt;code&gt;tool_use_id&lt;/code&gt;. An immediate acknowledgment never counts. At &lt;code&gt;Stop&lt;/code&gt; time, the hook computes &lt;code&gt;pending = launched − resolved&lt;/code&gt;; 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.&lt;/p&gt;

&lt;p&gt;I'd expected to just use Claude Code's native &lt;code&gt;SubagentStop&lt;/code&gt; hook for this and skip the transcript parsing entirely. It turns out that hook can't do the job: background agents (&lt;code&gt;run_in_background=true&lt;/code&gt;) bypass &lt;code&gt;Stop&lt;/code&gt;/&lt;code&gt;SubagentStop&lt;/code&gt; entirely, subagent completion isn't reliably reported through it, and even when it does fire, it carries the parent session's shared &lt;code&gt;session_id&lt;/code&gt; 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 &lt;code&gt;tool_use_id&lt;/code&gt; is the only signal that's actually reliable today.&lt;/p&gt;

&lt;h2&gt;
  
  
  The case I didn't build for
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Instead my phone buzzed three times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Claude Code stopped with error | Refactor the transcript parser | ~/work/foo | 11/07/2026 10:29:28
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;— 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like now
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Jeromefromcn/claude-code-notify/main/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One dependency (&lt;code&gt;python3&lt;/code&gt;), and config lives outside &lt;code&gt;settings.json&lt;/code&gt; so your token never gets committed by accident. Changed your mind? The same line with &lt;code&gt;--uninstall&lt;/code&gt; pulls the hooks and code back out (it leaves your &lt;code&gt;config.env&lt;/code&gt; alone, so reinstalling later doesn't mean re-entering your token):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/Jeromefromcn/claude-code-notify/main/install.sh | bash &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--uninstall&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's MIT-licensed: &lt;a href="https://github.com/Jeromefromcn/claude-code-notify" rel="noopener noreferrer"&gt;github.com/Jeromefromcn/claude-code-notify&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>cli</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
