AI & Agents

Get a Claude Code Push Notification When a Task Finishes

Get a Claude Code push notification the moment a task finishes, wire a one-line Stop hook to your phone. Free tier, no polling, setup in 5 minutes.

Red and black halftone illustration: an empty workshop bench at night, one task light on, a single finished part still clamped in the vise (TheNotificationApp)

You give Claude Code a big job ("migrate the whole test suite to Vitest"), and it starts grinding through forty files. So you tab over to Slack. Twenty minutes later it's been finished for eighteen of them, quietly sitting in a terminal you're not looking at.

That gap is the annoying part. The agent is fast; your attention is the bottleneck. You either babysit the terminal, which defeats the whole point of an agent, or you check back every few minutes and find it wrapped up ages ago. Either way you're the slow link in the loop.

Claude Code has a fix built in that most people never touch: hooks. A hook is a shell command Claude Code runs automatically when something happens, and one of those somethings is "Claude finished responding." Wire that to a single curl call and your phone buzzes the moment a run ends. No polling, no second terminal, no dashboard to watch. Here's the whole thing.

What you'll need

  • Claude Code, recent enough to have the hooks block in settings.json (any current version does).
  • A free TheNotificationApp account and an app_key. The free tier is $0 and gives you 100 notifications to test with.
  • That's it. The API is one HTTP POST: title, body, done. The full request shape lives in the curl docs.

One fork in the road first: hooks are a Claude Code thing. If you're on Claude Desktop instead, you'd wire this up through an MCP connector: different mechanism, different post. Here we're in the terminal, where a hook beats everything else for "just tell me when it's done."

The one-line version

The Stop hook fires when Claude Code finishes responding. Drop this into your settings.json: use ~/.claude/settings.json to cover every project, or .claude/settings.json for just the current repo:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "curl -s -X POST https://thenotification.app/api/sendNotification -H \"app_key: $NOTIFICATION_API_KEY\" -H \"Content-Type: application/json\" -d '{\"title\": \"Claude Code is done\", \"body\": \"Your task finished, come back to the terminal.\"}'"
          }
        ]
      }
    ]
  }
}

Then export your key where Claude Code can see it. Add this to your ~/.zshrc (or ~/.bashrc):

export NOTIFICATION_API_KEY="your_app_key_here"

Restart your shell so the variable is loaded, launch claude from that same shell, and give it something to chew on. When it stops, your phone buzzes. That's the entire integration: one hook, one env var.

Send the actual summary, not just "done"

"Claude Code is done" works, but you can do better. The Stop hook hands your command a JSON payload on stdin, and one field is last_assistant_message: the final thing Claude said. Pull it out and the notification tells you what actually happened.

Save this as ~/.claude/notify-done.sh:

#!/usr/bin/env bash
# Reads the Stop hook payload on stdin and pings your phone with a summary.
payload=$(cat)
summary=$(printf '%s' "$payload" | jq -r '.last_assistant_message // "Task finished"' | head -c 140)

curl -s -X POST https://thenotification.app/api/sendNotification \
  -H "app_key: $NOTIFICATION_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg body "$summary" '{title: "Claude Code is done", body: $body}')"

Make it executable:

chmod +x ~/.claude/notify-done.sh

Then point the hook at the script instead of the inline curl:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": "$HOME/.claude/notify-done.sh" }
        ]
      }
    ]
  }
}

Now the push reads something like "Migrated 40 test files to Vitest, all green" instead of a generic ping. Building the body with jq -n matters here: it escapes any quotes, apostrophes, or newlines in Claude's message so they can't break your JSON. One extra line, no surprises.

Ping me when it needs me, not just when it's done

There's a second hook worth knowing. Stop fires when a task finishes; Notification fires when Claude Code wants your attention mid-run, it hit a permission prompt, or it's been idle waiting for input. If you walk off during a long session, that's the one that saves you from returning to an agent frozen on "Allow this command?"

{
  "hooks": {
    "Notification": [
      {
        "matcher": "agent_needs_input",
        "hooks": [
          {
            "type": "command",
            "command": "curl -s -X POST https://thenotification.app/api/sendNotification -H \"app_key: $NOTIFICATION_API_KEY\" -H \"Content-Type: application/json\" -d '{\"title\": \"Claude Code needs you\", \"body\": \"It is waiting for input or a permission.\"}'"
          }
        ]
      }
    ]
  }
}

Same pattern, different trigger. Run both and you've got the two moments that matter covered: "I'm done" and "I'm stuck."

Real talk

The free tier caps at 100 notifications total, not per month, total. The Stop hook fires every time Claude finishes a turn, including the quick one-line back-and-forths, so if you leave it on for everyday chatting you'll burn through 100 in an afternoon. Two ways around it: keep the hook in a project-level .claude/settings.json you only enable for the big overnight runs, or move to Pro at $2.99/month for 1,000 a month. For "ping me when the long migration ends," the free tier is plenty, just don't wire it to a session where you're firing off forty messages an hour.

That's it

One curl, one hook, and Claude Code stops being something you have to watch. Kick off the big refactor, go get lunch, and let your phone tell you when to come back. Worth a look if you're tired of alt-tabbing to a terminal that finished ten minutes ago: start free at thenotification.app.

New to this? Start with what an MCP server is.