Skip to content
← All guides
Guide

Using the Claude Code Stop Hook to Trigger a Real Notification

September 21, 2026

Claude Code's hooks are the clean, supported way to run your own command in response to what the agent does. If you want to be told when a run ends — or when it's sitting there waiting on you — this is the mechanism to use, and it takes about five minutes to set up.

This is a developer's guide: what the relevant hooks are, exactly where the config lives, and how to escalate from a silent stop to something you'll actually notice.

The two hooks that matter for notifications

Claude Code fires hooks at defined lifecycle events. Two are relevant here:

  • Stop — runs when the agent finishes responding and the turn ends. This is your "the run is done" signal.
  • Notification — runs when Claude Code wants your attention, including when it's waiting for your input. This is your "it's blocked" signal — the expensive one.

Wiring both means you cover completion and the mid-run stall, which are different problems (the first is a heads-up, the second is idle-agent minutes ticking by).

Where the config lives

Hooks are declared in your Claude Code settings JSON — ~/.claude/settings.json for user-wide, or .claude/settings.json in a project. The shape is an event name, an optional matcher, and a list of shell commands to run:

json
{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "<your notify command>" }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          { "type": "command", "command": "<your notify command>" }
        ]
      }
    ]
  }
}

The command is just a shell command. Whatever you can run in a terminal, you can run here. Hooks receive event data on stdin as JSON, so a script can read the context if it needs to.

Level 1: a local ping

The simplest useful command is a desktop notification so a background terminal still gets your attention:

json
{ "type": "command", "command": "osascript -e 'display notification \"Claude Code stopped\" with title \"Claude Code\"'" }

(On Linux, notify-send "Claude Code" "stopped".) This works only while you're at the machine — fine as a baseline, useless once you leave.

Level 2: push to your phone

Swap the command for a one-line HTTP call to a push service so the alert leaves the machine. With ntfy.sh:

json
{ "type": "command", "command": "curl -s -d 'Claude Code needs you' ntfy.sh/your-topic-name" }

Pushover, Slack incoming webhooks, and Telegram bots all work the same way — one curl to their endpoint. Put this on the Notification hook and you'll get a push to your pocket the moment the agent starts waiting on you.

This is where most setups stop, and for "it finished" it's the right place to stop. The limitation is that a push is one-way: it tells you the agent is blocked, but you still have to get back to the machine and type the answer before it moves. If the decision could've been a ten-second "yes, ship it," you've turned it into a context switch.

Level 3: escalate the blocked case to a phone call

For the Notification/waiting case specifically, the higher tier is a channel that can carry your answer back, not just alert you. That's what AgentCall does: it's a pure-MCP voice bridge, so when the agent needs a decision it rings your phone, you talk it through, and the words you speak become the answer it was blocked on. No walk back to the terminal.

Because AgentCall is an MCP server, the most direct pattern is the agent itself calling the request_human tool when it hits a decision — no hook required, and the reply flows straight back into the run. The Stop/Notification hooks complement that for the "I stopped and you should know" cases: a hook can register a pending call request that the running agent picks up and acts on.

The exact hook command and the callback flow (queued requests, stand_by, voicemail) are documented and kept current here rather than pasted into a blog post that would drift:

AgentCall callbacks & the Claude Code Stop hook

Connecting the MCP itself is one command:

bash
claude mcp add --transport http --scope user agentcall https://agentcall.io/api/mcp

Sign in with Google, add AgentCall to your phone's home screen so it can ring, and you're set. Free in beta, speech minutes included — no card required.

A sensible two-tier setup

  • Stop hook → push (ntfy/Pushover/Slack). "The run is done." One-way is fine; you'll look when you look.
  • Notification hook + request_human → phone call. "It's blocked on a decision." Two-way, because the answer is the whole point.

Wire both once in ~/.claude/settings.json and you stop discovering forty-minute stalls after the fact.

Naming note: this is AgentCall.io (voice escalation for coding agents), not agentcall.co (an unrelated SMS/voice API SDK for building telephony into apps).

Next: The callbacks reference → · Get notified when Claude Code finishes or gets stuck →