<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Inkstone</title><link>https://me.125520.xyz/</link><description>A minimal bilingual Hugo template for longreads.</description><generator>Hugo</generator><language>en</language><managingEditor>Jason</managingEditor><copyright>© 2026 Jason</copyright><lastBuildDate>Fri, 08 May 2026 12:39:34 +0000</lastBuildDate><atom:link href="https://me.125520.xyz/en/tags/%E5%B7%A5%E5%85%B7/index.xml" rel="self" type="application/rss+xml"/><item><title>Stanford CS146S Notes: An 8-Week Map from Prompt to Agent Manager</title><link>https://me.125520.xyz/en/cs146s-modern-software-dev-notes/</link><pubDate>Fri, 08 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/en/cs146s-modern-software-dev-notes/</guid><description>My notes on Stanford CS146S, The Modern Software Developer. Eight weeks of assignments, readings, and guest lectures collapsed into three storylines: Prompt → Context, Coder → Agent Manager, Speed → Defensible.</description><content:encoded><![CDATA[<blockquote>
<p><a href="https://themodernsoftware.dev/" target="_blank" rel="noopener">CS146S — The Modern Software Developer<span class="external-mark" aria-hidden="true">↗</span></a>
 is a new Stanford course from Fall 2025, taught by Mihail Eric, with a heavyweight guest each week (Boris Cherny, Zach Lloyd, Isaac Evans, Tomas Reimers&hellip;). I worked through all eight weeks in the official <a href="https://github.com/mihail911/modern-software-dev-assignments" target="_blank" rel="noopener">assignments repo<span class="external-mark" aria-hidden="true">↗</span></a>
 and did the exercises along the way. This post is the consolidated note.</p>
</blockquote>
<h2 id="1-the-courses-real-theme-a-shift-in-developer-identity">1. The Course&rsquo;s Real Theme: A Shift in Developer Identity</h2>
<p>The tagline on the course site is unusually direct:</p>
<blockquote>
<p>&ldquo;Many AI coding courses only teach you how to write fast — this course draws the baseline for shipping: testable, auditable, defensible.&rdquo;</p>
</blockquote>
<p>If you only skim the weekly schedule, you&rsquo;d think it&rsquo;s &ldquo;An AI Tools Survey.&rdquo; But after reading the materials and doing the assignments, a more honest description is:</p>
<p><strong>It is systematically training you to become an Agent Manager.</strong></p>
<p>Not &ldquo;learn to code with ChatGPT,&rdquo; but: stop being the person who writes the code and become the person who directs Agents to write it — and who owns the result. Boris Cherny (creator of Claude Code) gets quoted again and again:</p>
<blockquote>
<p>&ldquo;It&rsquo;s not so much about deep work, it&rsquo;s about how good I am at context switching and jumping across multiple different contexts very quickly.&rdquo;</p>
</blockquote>
<p>The eight weeks fall into three storylines:</p>
<ol>
<li><strong>Prompt → Context</strong> (Weeks 1 → 3): from writing one good prompt to architecting a whole information environment</li>
<li><strong>Coder → Agent Manager</strong> (Weeks 2 → 5): from writing code yourself to directing and orchestrating multiple Agents</li>
<li><strong>Speed → Defensible</strong> (Weeks 6 → 8): from rapid output to auditable, defensible, ship-ready</li>
</ol>
<p>The rest of the post follows those three lines.</p>
<h2 id="2-storyline-one-prompt--context">2. Storyline One: Prompt → Context</h2>
<h3 id="week-1-six-foundational-prompting-techniques">Week 1: Six Foundational Prompting Techniques</h3>
<p>Week 1 runs local models through Ollama and drills six techniques in turn: K-shot, Chain-of-Thought, Tool Calling, Self-consistency, RAG, Reflexion. One Python file per technique, iterate the prompt until tests pass.</p>
<p>The takeaway isn&rsquo;t &ldquo;I learned six tricks.&rdquo; It&rsquo;s: <strong>these aren&rsquo;t parallel options, they form an evolutionary chain.</strong></p>
<ul>
<li>K-shot gives the model a pattern</li>
<li>CoT gives it room to reason</li>
<li>Tool Calling lets it stop guessing and actually look things up</li>
<li>Self-consistency trades sampling cost for stability</li>
<li>RAG plugs external knowledge into the context</li>
<li>Reflexion lets the model critique its own output and try again</li>
</ul>
<p>By Reflexion, the model has gone from &ldquo;answer machine&rdquo; to &ldquo;small self-correcting loop.&rdquo; That is the seed of an Agent.</p>
<h3 id="week-2-first-time-closing-the-loop-on-a-real-task">Week 2: First Time Closing the Loop on a Real Task</h3>
<p>The assignment is a minimal FastAPI + SQLite Action Item Extractor: free-form notes → structured todo list. Use Cursor&rsquo;s Agentic Mode to land five TODOs: replace rule-based extraction with an LLM, add unit tests, refactor, add a new endpoint + frontend button, generate a README.</p>
<p>In my own practice notes I distilled a minimum capability model for &ldquo;a local Agent that executes simple requirements&rdquo;:</p>
<pre tabindex="0"><code>understand intent → fetch code context → plan → generate code → verify → feedback
        ↑                                                                  |
        └────────────── on failure, backtrack &amp; correct ←─────────────────┘
</code></pre><p>Each step maps onto a Week 1 technique. Agents are not a new invention; they are those atomic capabilities wired up around this loop.</p>
<p>After running it end-to-end the strongest impression: <strong>today&rsquo;s Agents shine on tasks with clear boundaries that you can verify.</strong> Scaffolding, single-function implementations, happy-path tests, doc generation — these are strengths. Deciding &ldquo;what <em>not</em> to change,&rdquo; reasoning about cross-module side effects, refactors that involve real tradeoffs — those still need a human.</p>
<h3 id="week-3-from-prompt-engineering-to-context-engineering">Week 3: From Prompt Engineering to Context Engineering</h3>
<p>Week 3 is the densest week of the course (the other one being Week 6). Boris Cherny is the guest. The thesis traces back to Andrej Karpathy&rsquo;s definition:</p>
<blockquote>
<p>&ldquo;Context engineering is the delicate art and science of filling the context window with just the right information for the next step.&rdquo;</p>
</blockquote>
<table>
  <thead>
      <tr>
          <th>Dimension</th>
          <th>Prompt Engineering</th>
          <th>Context Engineering</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Focus</td>
          <td>What you say to the model right now</td>
          <td>What the model knows when you say it</td>
      </tr>
      <tr>
          <td>Scope</td>
          <td>One carefully crafted instruction</td>
          <td>The whole information ecosystem: memory, tools, retrieval</td>
      </tr>
      <tr>
          <td>Nature</td>
          <td>Static template</td>
          <td>Dynamic infrastructure</td>
      </tr>
      <tr>
          <td>Analogy</td>
          <td>Handing a script to an improv actor</td>
          <td>Building the whole stage, props included</td>
      </tr>
  </tbody>
</table>
<p>Two things in this week made me re-examine how I work:</p>
<p><strong>&ldquo;Context is King&rdquo; — and a precondition for intelligence.</strong> A line that hits hard: &ldquo;Claude is already smart enough — intelligence isn&rsquo;t the bottleneck, context is. When the Agent fails, the instinct is to blame the model, but it&rsquo;s almost always wrong. What&rsquo;s actually underperforming is the information environment around it.&rdquo;</p>
<p><strong>Context Rot.</strong> Model performance degrades with input length, even on simple tasks. So <code>/clear</code>, <code>/compact</code>, tiered memory, keeping CLAUDE.md lean — these aren&rsquo;t aesthetic choices, they&rsquo;re performance strategies.</p>
<p>The Week 3 assignment is to build a custom MCP Server. I built a GitHub Issue assistant: 3 Tools (<code>search_issues</code> / <code>create_issue</code> / <code>get_issue_detail</code>) + 1 Resource (repo metadata) + 1 Prompt (bug report template), STDIO transport into Claude Desktop.</p>
<p>Building it by hand is the only way I really understood: <strong>the three MCP primitives (Tools / Resources / Prompts) aren&rsquo;t abstract concepts — they map to three different ways an Agent &ldquo;knows&rdquo; something</strong> — what it can do, what it can look up, how it should phrase things.</p>
<p>Boris also shared his daily workflow: 20–30 PRs a day, sustained by running 5 Claude Code instances in terminals plus 5–10 sessions on claude.ai/code, every instance starting in Plan Mode, with CLAUDE.md as shared team memory. The line that gets hammered home:</p>
<blockquote>
<p>&ldquo;Give Claude a way to verify its own work — browser tests, test suites, simulators. Verification is what gives you a 2-3x quality improvement on the final output. Everything else (subagents, slash commands, MCP integrations) is built on top of that.&rdquo;</p>
</blockquote>
<h2 id="3-storyline-two-coder--agent-manager">3. Storyline Two: Coder → Agent Manager</h2>
<h3 id="week-4-claude-code-as-agent-manager">Week 4: Claude Code as Agent Manager</h3>
<p>Week 4&rsquo;s central line:</p>
<blockquote>
<p>&ldquo;The goal is to train you as an Agent Manager — not someone who writes code, but someone who directs Agents to write code.&rdquo;</p>
</blockquote>
<p>This week catalogs 5 agentic workflow patterns:</p>
<ul>
<li><strong>Sequential Flow</strong> — the default. About 70% of daily work.</li>
<li><strong>Operator</strong> — hierarchical: a central planner + specialized sub-agents</li>
<li><strong>Split-and-Merge</strong> — fan-out / fan-in, up to ~10 parallel branches</li>
<li><strong>Agent Teams</strong> — collaborative; agents can talk to each other across independent sessions</li>
<li><strong>Headless</strong> — triggered by CI/CD, Cron, webhooks; no human in the loop</li>
</ul>
<p>Plus 12 &ldquo;Agent Harness Patterns&rdquo; (persistent instructions, directory-scoped instructions, tiered memory, memory hygiene, context compaction, parallel branching, checkpoint review, selective tool loading, purpose-built tools, permission granularity, lifecycle hooks, compounding corrections).</p>
<p>The line that stuck:</p>
<blockquote>
<p>&ldquo;The problem isn&rsquo;t your model — it&rsquo;s your harness. What separates a flashy demo from a production-grade AI system isn&rsquo;t intelligence, it&rsquo;s control.&rdquo;</p>
</blockquote>
<p>The assignment: build at least 2 automations (Slash Commands, CLAUDE.md, SubAgents) on a FastAPI starter app. After this week my muscle memory shifted: every time I see a repeated workflow I now ask &ldquo;could this become a <code>.claude/commands/</code> entry?&rdquo;; every time Claude makes the same mistake twice I ask &ldquo;should this go into CLAUDE.md?&rdquo;</p>
<p>The phrase <strong>Compounding Engineering</strong> is one I like a lot: every recorded mistake becomes permanent leverage.</p>
<h3 id="week-5-the-terminal-is-not-a-power-users-toy">Week 5: The Terminal Is Not a Power User&rsquo;s Toy</h3>
<p>Week 5&rsquo;s guest is Warp CEO Zach Lloyd. Warp 2.0 reframes itself from &ldquo;a better terminal&rdquo; to ADE — Agentic Development Environment.</p>
<p>The comparison table I found most useful:</p>
<table>
  <thead>
      <tr>
          <th>Tool</th>
          <th>Strength</th>
          <th>Weakness</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Warp</strong></td>
          <td>Multi-tab parallel agents, visual diff review, multi-model support</td>
          <td>Slightly weaker at deep code reasoning</td>
      </tr>
      <tr>
          <td><strong>Claude Code</strong></td>
          <td>Deep context, fine-grained CLAUDE.md control, autonomous reasoning</td>
          <td>Anthropic models only</td>
      </tr>
  </tbody>
</table>
<p>These stack rather than compete: <strong>Claude Code can run inside Warp</strong>. The key tool for parallel Agents is <code>git worktree</code> — each Agent works in an isolated working tree, no clobbering, merge branches at the end. I started using this in real work after reading this week.</p>
<p>But the Warp team is honest about the cost: <strong>parallelism isn&rsquo;t free.</strong> Saving 6–7 hours a week is real, but only if your cognitive bandwidth can actually steer 5 tabs at once — that turns out to be the real bottleneck.</p>
<h2 id="4-storyline-three-speed--defensible">4. Storyline Three: Speed → Defensible</h2>
<h3 id="week-6-semgrep-and-ai-code-security">Week 6: Semgrep and AI Code Security</h3>
<p>Week 6 brings Semgrep CEO Isaac Evans. The opening number made me pause:</p>
<blockquote>
<p>Claude Code, run as a security scanner, surfaced 46 real vulnerabilities — at a <strong>14% true positive rate</strong> and <strong>86% false positive rate</strong>.</p>
</blockquote>
<p>This isn&rsquo;t &ldquo;Claude is bad.&rdquo; It&rsquo;s that <strong>AI&rsquo;s security intuition isn&rsquo;t reliable — out of 7 alerts only 1 is a real issue.</strong> Semgrep flips that ratio: 80% fewer false positives, 250% more true positives.</p>
<p>Semgrep&rsquo;s three products:</p>
<ul>
<li><strong>Code (SAST)</strong> — first-party code vulns, with cross-file analysis</li>
<li><strong>Supply Chain (SCA)</strong> — <em>reachable</em> CVEs in dependencies (reachability analysis filters out unreachable ones, cutting high/critical-severity false positives by 98%)</li>
<li><strong>Secrets</strong> — hardcoded credentials, with semantic + entropy analysis + live validation</li>
</ul>
<p>The assignment: run <code>semgrep ci --subdir week6</code> on a deliberately vulnerable FastAPI app and fix at least 3 findings. The fixes are unglamorous but fundamental: parameterized SQL, restricted CORS, secrets to env vars, dependency upgrades, stronger crypto, sanitized DOM writes.</p>
<p>The blunt takeaway: <strong>&ldquo;Secure Vibe Coding&rdquo; wants speed and safety unified, not opposed</strong> — the way you get there is automating scans into CI rather than retrofitting safety after the fact.</p>
<h3 id="week-7-graphite-diamond-and-ai-code-review">Week 7: Graphite Diamond and AI Code Review</h3>
<p>Week 7&rsquo;s guest is Graphite CPO Tomas Reimers. The thesis:</p>
<blockquote>
<p>&ldquo;AI will never replace human code review. But every PR should be reviewed by AI + human together.&rdquo;</p>
</blockquote>
<p>AI&rsquo;s strengths in review: style consistency, common bug patterns, security vulnerabilities, code duplication, 100% PR coverage, second-level latency.
Human strengths: architectural judgment, business logic correctness, tradeoff analysis, creative alternatives, team context, UX design.</p>
<p>The recommended pattern is &ldquo;AI First, Human Second&rdquo;: AI filters the mechanical stuff, humans focus on the high-leverage judgment calls. Graphite&rsquo;s other product innovation is <strong>Stacked PRs</strong> — break a big PR into a chain of small sequential ones, because AI review performs noticeably better on small PRs.</p>
<p>The assignment is dense: 4 independent tasks, each going through &ldquo;create branch → 1-shot AI implementation → manual line-by-line review → open PR → run Graphite Diamond → compare.&rdquo; Then a written reflection: my review vs Graphite&rsquo;s, which one was better, when, and why.</p>
<p>That <strong>comparative reflection</strong> is the most valuable part of the week, in my view. It isn&rsquo;t teaching you to trust AI. It&rsquo;s teaching you <strong>to know when AI is better than you, and when it isn&rsquo;t.</strong> That meta-cognition is more durable than any specific tool.</p>
<h3 id="week-8-boltnew-and-multi-stack-rapid-builds">Week 8: Bolt.new and Multi-Stack Rapid Builds</h3>
<p>Week 8 pivots to &ldquo;prototype to production.&rdquo; The guest is Vercel&rsquo;s Head of AI Research. The framing:</p>
<blockquote>
<p>&ldquo;Rapid prototyping is just the starting point. Between demo and production lies a chasm.&rdquo;</p>
</blockquote>
<p>Bolt.new uses StackBlitz&rsquo;s WebContainer technology to run Node.js entirely in the browser, generating full-stack apps from prompts. But its capability curve is steep:</p>
<table>
  <thead>
      <tr>
          <th>Complexity</th>
          <th>Result</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>3–5 components</td>
          <td>Usable in minutes, almost no debugging</td>
      </tr>
      <tr>
          <td>10–15 components</td>
          <td>Needs prompt iteration, but generally works</td>
      </tr>
      <tr>
          <td>15–20+ components</td>
          <td>Context degrades, token use spikes</td>
      </tr>
      <tr>
          <td>Complex state + auth + 3rd-party integrations</td>
          <td>Success rate drops to <strong>31%</strong></td>
      </tr>
  </tbody>
</table>
<p>The assignment asks you to build the same app in 3 different stacks, at least one via Bolt and at least one in a non-JS language. The design is sneakily clever — it trains two things at once:</p>
<ol>
<li><strong>Abstraction</strong> — you have to pull the business logic out of the framework and figure out what&rsquo;s essence vs implementation</li>
<li><strong>AI-tool literacy</strong> — the <em>same</em> prompt produces wildly different quality across Next.js / Django / Rails, so you have to develop a feel for &ldquo;what does production-ready code actually look like&rdquo;</li>
</ol>
<h2 id="5-three-lessons-i-actually-use-daily">5. Three Lessons I Actually Use Daily</h2>
<p>After eight weeks of material and a stack of assignments, these are the three I&rsquo;ve actually absorbed into my workflow:</p>
<h3 id="51-plan-mode-is-free-leverage">5.1 Plan Mode Is Free Leverage</h3>
<p>Boris&rsquo;s &ldquo;never let Claude write code before you have reviewed and approved a written plan&rdquo; is the most-quoted line of the course. For any task longer than 30 minutes I now hit <code>shift+tab</code> into Plan Mode, iterate with Claude until the plan is right, then switch to auto-accept. Rework rate dropped noticeably.</p>
<h3 id="52-claudemd-is-where-compounding-lives">5.2 CLAUDE.md Is Where Compounding Lives</h3>
<p>Every time Claude makes a mistake or I notice a non-obvious project convention, it goes into CLAUDE.md. The habit is incredibly cheap and incredibly profitable — once it&rsquo;s in there I never explain it again. Just keep it lean: it loads every session and eats context.</p>
<h3 id="53-always-give-the-agent-a-way-to-verify-itself">5.3 Always Give the Agent a Way to Verify Itself</h3>
<p>Boris&rsquo;s &ldquo;most important insight.&rdquo; Concretely:</p>
<ul>
<li>Backend work: have Claude run the tests, fail-then-fix, never &ldquo;looks fine, ship it&rdquo;</li>
<li>Frontend work: spin up the dev server and have Claude actually click through the golden path with Playwright</li>
<li>API integrations: get a minimal curl/httpie reproduction script first</li>
</ul>
<p>This is just the engineering version of Reflexion (Week 1) — bake &ldquo;self-review&rdquo; directly into the workflow.</p>
<h2 id="6-closing-the-eight-week-arc">6. Closing: The Eight-Week Arc</h2>
<pre tabindex="0"><code>Week 1:  How to talk to AI                  → Prompt Engineering
Week 2:  How AI uses tools                  → Coding Agents &amp; MCP
Week 3:  How to build AI&#39;s information env  → Context Engineering
Week 4:  How to manage AI                   → Agent Manager
Week 5:  AI-native terminal                 → AI Terminal
Week 6:  AI code security                   → Semgrep
Week 7:  AI code review                     → Graphite Diamond
Week 8:  AI-built applications              → Bolt.new multi-stack
(Weeks 9-10: AI ops + the future            → second half of the course)
</code></pre><p>Each week is the foundation for the next. What you walk away with isn&rsquo;t a tool manual — it&rsquo;s a new posture:</p>
<p><strong>You aren&rsquo;t the person writing code anymore. You&rsquo;re the person designing the information environment, directing the Agents, and answering for the final output.</strong></p>
<p>Coding speed is up 10x. So is the speed at which you can ship the wrong thing. The baseline this course draws — <strong>testable, auditable, defensible</strong> — isn&rsquo;t a constraint. It&rsquo;s the floor that makes the new posture viable.</p>
<hr>
<p><strong>References:</strong></p>
<ul>
<li>Course homepage: <a href="https://themodernsoftware.dev/" target="_blank" rel="noopener">themodernsoftware.dev<span class="external-mark" aria-hidden="true">↗</span></a>
</li>
<li>Assignments repo: <a href="https://github.com/mihail911/modern-software-dev-assignments" target="_blank" rel="noopener">mihail911/modern-software-dev-assignments<span class="external-mark" aria-hidden="true">↗</span></a>
</li>
<li>Community Chinese edition: <a href="https://github.com/ShouZhengAI/CS146S_CN" target="_blank" rel="noopener">ShouZhengAI/CS146S_CN<span class="external-mark" aria-hidden="true">↗</span></a>
</li>
<li>Claude Code best practices: <a href="https://www.anthropic.com/engineering/claude-code-best-practices" target="_blank" rel="noopener">anthropic.com/engineering/claude-code-best-practices<span class="external-mark" aria-hidden="true">↗</span></a>
</li>
<li>12 Agentic Harness Patterns: <a href="https://generativeprogrammer.com/p/12-agentic-harness-patterns-from" target="_blank" rel="noopener">generativeprogrammer.com<span class="external-mark" aria-hidden="true">↗</span></a>
</li>
<li>How Anthropic Teams Use Claude Code: <a href="https://claude.com/blog/how-anthropic-teams-use-claude-code" target="_blank" rel="noopener">claude.com/blog<span class="external-mark" aria-hidden="true">↗</span></a>
</li>
<li>Andrej Karpathy on Context Engineering (original X post)</li>
<li>Boris Cherny&rsquo;s workflow: <a href="https://howborisusesclaudecode.com" target="_blank" rel="noopener">howborisusesclaudecode.com<span class="external-mark" aria-hidden="true">↗</span></a>
</li>
</ul>
]]></content:encoded></item><item><title>Wiring Claude Code into macOS Notifications + Bark Push</title><link>https://me.125520.xyz/en/claude-code-mac-bark-notify/</link><pubDate>Thu, 07 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://me.125520.xyz/en/claude-code-mac-bark-notify/</guid><description>Translating a Windows PowerShell gist to macOS, then bolting on a Bark channel so the phone buzzes when long jobs finish too.</description><content:encoded><![CDATA[<blockquote>
<p>Origin story: spotted a <a href="https://gist.github.com/RisenMyth/6297cab96eb9dec912cc6c126cbed965" target="_blank" rel="noopener">Windows gist that wires Claude Code into system notifications<span class="external-mark" aria-hidden="true">↗</span></a>
, written in PowerShell, useless on a Mac. Rewrote it for macOS, then added a <a href="https://day.app/" target="_blank" rel="noopener">Bark<span class="external-mark" aria-hidden="true">↗</span></a>
 channel — when a long job finishes and you&rsquo;ve wandered off, the phone in your pocket should be the one that pings.</p>
</blockquote>
<h2 id="1-the-shape-of-it">1. The shape of it</h2>
<p>Claude Code&rsquo;s hook system exposes a set of lifecycle events. For &ldquo;notify me&rdquo;, you only need two:</p>
<ul>
<li><strong><code>Stop</code></strong> — the model finished its turn. Time to ring the bell.</li>
<li><strong><code>Notification</code></strong> — the model wants confirmation or input (permission prompts, questions). More urgent.</li>
</ul>
<p>The hook command receives JSON on stdin: <code>cwd</code> (project path), <code>transcript_path</code> (full JSONL conversation), and <code>Notification</code> events also carry a <code>message</code>.</p>
<p>Design choices:</p>
<ul>
<li><strong>Single Python file</strong> — macOS ships <code>python3</code> by default, so no jq / curl JSON-escaping ceremony.</li>
<li><strong>Local toast prefers <code>terminal-notifier</code></strong> — use it if installed (clickable, supports group dedup); fall back to <code>osascript</code> otherwise. Zero install required.</li>
<li><strong>Bark is opt-in</strong> — only fires when <code>BARK_KEY</code> is set. Failures are silent; the hook&rsquo;s exit code stays clean.</li>
<li><strong>No chmod</strong> — see the &ldquo;gotchas&rdquo; section below. The script isn&rsquo;t executable; we invoke it as <code>python3 path/to/notify.py</code>.</li>
</ul>
<h2 id="2-the-script-claudehooksnotifypy">2. The script: <code>~/.claude/hooks/notify.py</code></h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;Claude Code notification hook for macOS.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Local toast (osascript or terminal-notifier) + optional Bark push.
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Usage: notify.py &lt;Stop|Notification|...&gt;
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Reads JSON from stdin (Claude Code hook payload):
</span></span></span><span class="line"><span class="cl"><span class="s2">  cwd               -&gt; current working directory
</span></span></span><span class="line"><span class="cl"><span class="s2">  transcript_path   -&gt; path to JSONL transcript (Stop event)
</span></span></span><span class="line"><span class="cl"><span class="s2">  message           -&gt; notification message (Notification event)
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">Environment variables (all optional):
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_KEY            Bark device key. If unset, Bark push is skipped.
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_SERVER         Bark server (default: https://api.day.app).
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_GROUP          Notification group (default: ClaudeCode).
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_SOUND          Notification sound (default: minuet).
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_ICON           Custom icon URL.
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_STOP_LEVEL     Level for Stop events (default: passive).
</span></span></span><span class="line"><span class="cl"><span class="s2">  BARK_NOTIFY_LEVEL   Level for Notification events (default: timeSensitive).
</span></span></span><span class="line"><span class="cl"><span class="s2">                      Valid: active | timeSensitive | passive | critical.
</span></span></span><span class="line"><span class="cl"><span class="s2">  CLAUDE_NOTIFY_OFF   If set to &#34;1&#34;, suppress all notifications.
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">shutil</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">subprocess</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">urllib.request</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">TITLE_PREFIX</span> <span class="o">=</span> <span class="s2">&#34;ClaudeCode&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">MAX_BODY_LEN</span> <span class="o">=</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">read_stdin_json</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">dict</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">raw</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">raw</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">last_assistant_text</span><span class="p">(</span><span class="n">transcript_path</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">p</span> <span class="o">=</span> <span class="n">Path</span><span class="p">(</span><span class="n">transcript_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">p</span><span class="o">.</span><span class="n">is_file</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">p</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">&#34;r&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">,</span> <span class="n">errors</span><span class="o">=</span><span class="s2">&#34;replace&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">lines</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">readlines</span><span class="p">()[</span><span class="o">-</span><span class="mi">30</span><span class="p">:]</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="nb">reversed</span><span class="p">(</span><span class="n">lines</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">entry</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="cl">        <span class="n">msg</span> <span class="o">=</span> <span class="n">entry</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;message&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">msg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;role&#34;</span><span class="p">)</span> <span class="o">!=</span> <span class="s2">&#34;assistant&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">continue</span>
</span></span><span class="line"><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">msg</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;content&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">text</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="nb">str</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="n">text</span> <span class="o">=</span> <span class="n">content</span>
</span></span><span class="line"><span class="cl">        <span class="k">elif</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">content</span><span class="p">,</span> <span class="nb">list</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="n">block</span> <span class="ow">in</span> <span class="n">content</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">block</span><span class="p">,</span> <span class="nb">dict</span><span class="p">)</span> <span class="ow">and</span> <span class="n">block</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;type&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;text&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                    <span class="n">t</span> <span class="o">=</span> <span class="n">block</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="n">t</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">                        <span class="n">text</span> <span class="o">=</span> <span class="n">t</span>
</span></span><span class="line"><span class="cl">                        <span class="k">break</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">text</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">text</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">truncate</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">s</span> <span class="o">=</span> <span class="p">(</span><span class="n">s</span> <span class="ow">or</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">n</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">s</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">s</span><span class="p">[:</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="s2">&#34;…&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">send_mac_notification</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">body</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">tn</span> <span class="o">=</span> <span class="n">shutil</span><span class="o">.</span><span class="n">which</span><span class="p">(</span><span class="s2">&#34;terminal-notifier&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">tn</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="p">[</span><span class="n">tn</span><span class="p">,</span> <span class="s2">&#34;-title&#34;</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="s2">&#34;-message&#34;</span><span class="p">,</span> <span class="n">body</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                 <span class="s2">&#34;-sound&#34;</span><span class="p">,</span> <span class="s2">&#34;Glass&#34;</span><span class="p">,</span> <span class="s2">&#34;-group&#34;</span><span class="p">,</span> <span class="s2">&#34;claude-code&#34;</span><span class="p">,</span> <span class="s2">&#34;-ignoreDnD&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">                <span class="n">check</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">DEVNULL</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">DEVNULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span>
</span></span><span class="line"><span class="cl">        <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">def</span> <span class="nf">esc</span><span class="p">(</span><span class="n">s</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">s</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\\</span><span class="s2">&#34;</span><span class="p">,</span> <span class="s2">&#34;</span><span class="se">\\\\</span><span class="s2">&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s1">&#39;&#34;&#39;</span><span class="p">,</span> <span class="s1">&#39;</span><span class="se">\\</span><span class="s1">&#34;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">script</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="sa">f</span><span class="s1">&#39;display notification &#34;</span><span class="si">{</span><span class="n">esc</span><span class="p">(</span><span class="n">body</span><span class="p">)</span><span class="si">}</span><span class="s1">&#34; &#39;</span>
</span></span><span class="line"><span class="cl">        <span class="sa">f</span><span class="s1">&#39;with title &#34;</span><span class="si">{</span><span class="n">esc</span><span class="p">(</span><span class="n">title</span><span class="p">)</span><span class="si">}</span><span class="s1">&#34; sound name &#34;Glass&#34;&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="p">[</span><span class="s2">&#34;osascript&#34;</span><span class="p">,</span> <span class="s2">&#34;-e&#34;</span><span class="p">,</span> <span class="n">script</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">            <span class="n">check</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">DEVNULL</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">DEVNULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">send_bark_push</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">body</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">event</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">key</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_KEY&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">key</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">server</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_SERVER&#34;</span><span class="p">,</span> <span class="s2">&#34;https://api.day.app&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">event</span> <span class="o">==</span> <span class="s2">&#34;Notification&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">level</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_NOTIFY_LEVEL&#34;</span><span class="p">,</span> <span class="s2">&#34;timeSensitive&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">event</span> <span class="o">==</span> <span class="s2">&#34;Stop&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">level</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_STOP_LEVEL&#34;</span><span class="p">,</span> <span class="s2">&#34;passive&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">level</span> <span class="o">=</span> <span class="s2">&#34;active&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;title&#34;</span><span class="p">:</span> <span class="n">title</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;body&#34;</span><span class="p">:</span> <span class="n">body</span> <span class="ow">or</span> <span class="s2">&#34; &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;group&#34;</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_GROUP&#34;</span><span class="p">,</span> <span class="s2">&#34;ClaudeCode&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;level&#34;</span><span class="p">:</span> <span class="n">level</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;sound&#34;</span><span class="p">:</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_SOUND&#34;</span><span class="p">,</span> <span class="s2">&#34;minuet&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">icon</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;BARK_ICON&#34;</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="n">payload</span><span class="p">[</span><span class="s2">&#34;icon&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">icon</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">req</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">Request</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">server</span><span class="si">}</span><span class="s2">/</span><span class="si">{</span><span class="n">key</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">data</span><span class="o">=</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">ensure_ascii</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&#34;utf-8&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">method</span><span class="o">=</span><span class="s2">&#34;POST&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;Content-Type&#34;</span><span class="p">:</span> <span class="s2">&#34;application/json; charset=utf-8&#34;</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">with</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span> <span class="k">as</span> <span class="n">r</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">r</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">pass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;CLAUDE_NOTIFY_OFF&#34;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&#34;1&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">event</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">else</span> <span class="s2">&#34;Stop&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">read_stdin_json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">cwd</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;cwd&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">project</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">cwd</span><span class="p">)</span> <span class="k">if</span> <span class="n">cwd</span> <span class="k">else</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">event</span> <span class="o">==</span> <span class="s2">&#34;Stop&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">TITLE_PREFIX</span><span class="si">}</span><span class="s2"> - </span><span class="si">{</span><span class="n">project</span><span class="si">}</span><span class="s2">&#34;</span> <span class="k">if</span> <span class="n">project</span> <span class="k">else</span> <span class="n">TITLE_PREFIX</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">transcript</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;transcript_path&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">transcript</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">body</span> <span class="o">=</span> <span class="n">last_assistant_text</span><span class="p">(</span><span class="n">transcript</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="ow">not</span> <span class="n">body</span><span class="o">.</span><span class="n">strip</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="n">body</span> <span class="o">=</span> <span class="s2">&#34;Task completed, please review results.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">elif</span> <span class="n">event</span> <span class="o">==</span> <span class="s2">&#34;Notification&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">TITLE_PREFIX</span><span class="si">}</span><span class="s2"> - Needs Attention&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">project</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">title</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">title</span><span class="si">}</span><span class="s2"> - </span><span class="si">{</span><span class="n">project</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="p">(</span><span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;message&#34;</span><span class="p">)</span> <span class="ow">or</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> \
</span></span><span class="line"><span class="cl">            <span class="ow">or</span> <span class="s2">&#34;Claude is waiting for your input or approval.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">title</span> <span class="o">=</span> <span class="n">TITLE_PREFIX</span>
</span></span><span class="line"><span class="cl">        <span class="n">body</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Event received: </span><span class="si">{</span><span class="n">event</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">body</span> <span class="o">=</span> <span class="n">truncate</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="n">MAX_BODY_LEN</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">send_mac_notification</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">body</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">send_bark_push</span><span class="p">(</span><span class="n">title</span><span class="p">,</span> <span class="n">body</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="n">main</span><span class="p">())</span>
</span></span></code></pre></div><p>A few details worth flagging:</p>
<ul>
<li><strong>Walking the transcript backwards</strong> — read the last 30 JSONL lines, iterate in reverse, take the first entry where <code>role == &quot;assistant&quot;</code> with non-empty text. The <code>content</code> field can be either a string or an array depending on SDK version; both shapes are handled.</li>
<li><strong>Truncation uses <code>…</code></strong> — a single ellipsis character, not three ASCII dots. Saves two bytes for actual prose.</li>
<li><strong>Bark <code>level</code> differs by event</strong> — <code>Stop</code> defaults to <code>passive</code> (silent, just lands in the push list), <code>Notification</code> defaults to <code>timeSensitive</code> (pierces Focus mode). Long jobs finishing shouldn&rsquo;t shriek; mid-task confirmations must.</li>
<li><strong>Silent failure</strong> — Bark unreachable, osascript erroring, transcript missing — all caught and swallowed. A hook should never wedge the model.</li>
</ul>
<h2 id="3-wiring-it-into-claudesettingsjson">3. Wiring it into <code>~/.claude/settings.json</code></h2>
<p>Merge this block into <code>settings.json</code>, <strong>preserving your existing <code>env / permissions / enabledPlugins</code></strong> etc.:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;hooks&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Notification&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;matcher&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;hooks&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;python3 /Users/you/.claude/hooks/notify.py Notification&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;timeout&#34;</span><span class="p">:</span> <span class="mi">15</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Stop&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;matcher&#34;</span><span class="p">:</span> <span class="s2">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;hooks&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;command&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;python3 /Users/you/.claude/hooks/notify.py Stop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;timeout&#34;</span><span class="p">:</span> <span class="mi">15</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">]</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>matcher: &quot;&quot;</code> is &ldquo;match anything&rdquo; — <code>Stop</code> and <code>Notification</code> don&rsquo;t have sub-matchers anyway. The <code>timeout: 15</code> seconds is a backstop; timing out doesn&rsquo;t break the main flow.</p>
<div class="callout callout-warn" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></div>
  <div class="callout-body"><p class="callout-title">Path must be absolute</p>
    The hook command&rsquo;s working directory is unpredictable — it depends on which project Claude is sitting in at the time. <code>~/.claude/hooks/notify.py</code> won&rsquo;t tilde-expand here. Spell it out: <code>/Users/&lt;you&gt;/.claude/hooks/notify.py</code>.
  </div>
</div>

<h2 id="4-enabling-bark">4. Enabling Bark</h2>
<p>Bark is a long-running iOS push tool. The free key comes from <a href="https://day.app/" target="_blank" rel="noopener">day.app<span class="external-mark" aria-hidden="true">↗</span></a>
. Install the app and the home screen shows a URL like <code>https://api.day.app/abc123xyz/</code> — the middle slug is your device key.</p>
<p>Drop it into the <code>env</code> block of <code>settings.json</code> (hooks inherit env from there):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;env&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;BARK_KEY&#34;</span><span class="p">:</span> <span class="s2">&#34;abc123xyz&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Quick reference for the optional knobs:</p>
<table>
  <thead>
      <tr>
          <th>Variable</th>
          <th>Default</th>
          <th>Purpose</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>BARK_SERVER</code></td>
          <td><code>https://api.day.app</code></td>
          <td>Override for self-hosted Bark</td>
      </tr>
      <tr>
          <td><code>BARK_GROUP</code></td>
          <td><code>ClaudeCode</code></td>
          <td>Notification group (folds on phone)</td>
      </tr>
      <tr>
          <td><code>BARK_SOUND</code></td>
          <td><code>minuet</code></td>
          <td>Sound name</td>
      </tr>
      <tr>
          <td><code>BARK_STOP_LEVEL</code></td>
          <td><code>passive</code></td>
          <td>Level for &ldquo;task finished&rdquo; pushes</td>
      </tr>
      <tr>
          <td><code>BARK_NOTIFY_LEVEL</code></td>
          <td><code>timeSensitive</code></td>
          <td>Level for &ldquo;needs your input&rdquo; pushes</td>
      </tr>
      <tr>
          <td><code>CLAUDE_NOTIFY_OFF</code></td>
          <td>(unset)</td>
          <td>Set to <code>1</code> to mute everything temporarily</td>
      </tr>
  </tbody>
</table>
<h2 id="5-verification">5. Verification</h2>
<p>With <code>BARK_KEY</code> set, fire a manual Notification simulation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">BARK_KEY</span><span class="o">=</span>your_key <span class="nb">echo</span> <span class="s1">&#39;{&#34;cwd&#34;:&#34;/tmp&#34;,&#34;message&#34;:&#34;test&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  <span class="p">|</span> python3 ~/.claude/hooks/notify.py Notification
</span></span></code></pre></div><p>Expected:</p>
<ol>
<li>A macOS toast pops in the top-right corner.</li>
<li>A Bark notification arrives on your phone, grouped under <code>ClaudeCode</code>, level <code>timeSensitive</code>.</li>
</ol>
<p>To preview local-only behavior, just unset <code>BARK_KEY</code> for the call.</p>
<h2 id="6-gotchas">6. Gotchas</h2>
<div class="callout callout-warn" role="note">
  <div class="callout-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg></div>
  <div class="callout-body"><p class="callout-title">macOS notification permissions</p>
    <code>osascript display notification</code> posts under the identity of the <strong>terminal app that spawned it</strong> (Terminal / iTerm / Ghostty / Warp). If nothing appears at all: System Settings → Notifications → find the terminal in the list → allow notifications.
  </div>
</div>

<p><strong>Hook not firing?</strong> The Claude Code settings watcher only watches directories that already had a settings file at session start. If <code>.claude/</code> was created mid-session, edits may not reload. Open the <code>/hooks</code> menu once (forces a reload), or restart the Claude Code session.</p>
<p><strong><code>chmod</code> denied in your permissions?</strong> My settings include <code>Bash(chmod:*)</code> in <code>permissions.deny</code>, so the script can&rsquo;t be <code>chmod +x</code>&rsquo;d. Fix: invoke via <code>python3 path/to/notify.py</code> so the executable bit is irrelevant. Bonus — it&rsquo;s more explicit and removes a hidden piece of state.</p>
<p><strong>Body too long?</strong> The script caps at <code>MAX_BODY_LEN = 200</code> (199 chars + <code>…</code>). macOS Notification Center will truncate too, but capping early avoids ugly mid-JSON breakage on the Bark side.</p>
<p><strong>Want to dial it back?</strong> <code>Stop</code> fires every turn — easy to find noisy. Two ways out:</p>
<ul>
<li>The Bark side already uses <code>BARK_STOP_LEVEL = passive</code> — silent push, lands in the list only.</li>
<li>For local silence on Stop too: add <code>if event == &quot;Stop&quot;: return</code> to the top of <code>send_mac_notification</code>. Five-line change.</li>
</ul>
<h2 id="7-where-to-take-it-next">7. Where to take it next</h2>
<p>Saving these for future me:</p>
<ul>
<li><strong>Telegram / Lark / Slack</strong> — rename <code>send_bark_push</code> to <code>send_remote_push</code>, dispatch on a <code>REMOTE_KIND</code> env var. Bark is just one of many.</li>
<li><strong>Sharper Notification copy</strong> — currently I just pass through whatever <code>message</code> the model sent. Could branch on <code>tool_name</code> (if hook payload starts including it) for sharper titles.</li>
<li><strong>Debounce a burst</strong> — multiple <code>Stop</code> events within 5s collapsed into one. Right now every turn rings.</li>
<li><strong>Stats</strong> — append a line to <code>~/.claude/hooks/notify.log</code> per fire. A week&rsquo;s worth of &ldquo;how many turns ran, how often the model got blocked on input&rdquo; tells you something interesting.</li>
</ul>
<hr>
<p>You set this up once and forget about it. When the model finishes a turn or stalls waiting for input, a soft chime in the top-right and a buzz in your pocket — long jobs can finally run while you make a coffee.</p>
]]></content:encoded></item></channel></rss>