Troubleshootingclaude code notifications not workingclaude code hooks not firingclaude code notification setup

Claude Code Notifications Not Working? Here's How to Fix It

A step-by-step troubleshooting guide for when Claude Code notifications, hooks, or alerts stop firing — from settings.json mistakes to silent shells and iOS permissions.

Ian Brillantes · Founder & iOS EngineerJune 13, 20266 min read

Quick answer

Most Claude Code notification failures come from one of six causes: the hook is missing or malformed in settings.json, the session wasn't restarted after editing it, the webhook command itself fails, the hook runs in a non-interactive shell without your PATH, you hooked the wrong event, or iOS notification permissions are off. Check them in that order.

You set up Claude Code notifications, walked away confident, and came back to silence — the agent finished ten minutes ago and never told you. Notification problems almost always come down to one of a handful of causes. This guide walks through them in the order worth checking, from most common to most obscure.

Work top to bottom and you'll usually find the culprit within the first two or three steps. Resist the urge to rebuild your whole setup from scratch — the fix is almost always a single misplaced line or a session that needs restarting.

1. Confirm the hook is actually in settings.json

Info

Installed the Agentfy plugin instead of editing hooks by hand? Its hooks live inside the plugin, not in settings.json, so this file can look empty even when everything's wired correctly. Confirm the plugin is active with claude plugin list, run /reload-plugins, then skip to step 3 to test the webhook. The rest of this section applies to the manual settings.json setup.

The number-one cause is that the hook isn't where Claude Code looks for it. Check the file directly:

cat ~/.claude/settings.json

You should see a hooks block with your webhook command. A correctly wired notification hook looks roughly like this:

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "/usr/bin/curl -s -X POST \"YOUR_WEBHOOK_URL\" -H \"Content-Type: application/json\" -d '{\"state\":\"needs_you\"}'"
          }
        ]
      }
    ]
  }
}

Two common mistakes:

  • It's in the wrong file — project-level .claude/settings.json vs. global ~/.claude/settings.json. If you set it globally but run in a project that overrides hooks, yours won't fire.
  • The JSON is malformed. A trailing comma or missing bracket makes Claude Code silently ignore the whole block. Paste it into a JSON validator.
Heads up

Claude Code fails silent on bad hook config. It does not print an error when the JSON is invalid or the matcher is wrong — it simply skips the block. That's exactly why a notification can "disappear" after an edit you thought was harmless.

2. Restart Claude Code after editing hooks

Hooks are read when a session starts. If you edited settings.json while a session was already running, the old config is still in memory. Exit and restart Claude Code (you can /resume your conversation afterward). If you installed the Agentfy plugin, run /reload-plugins instead of restarting. This single step fixes a surprising number of "it's configured but nothing happens" reports.

3. Test the webhook command on its own

Isolate the problem: is it the hook firing, or the request failing? Run your curl command directly in the same shell Claude Code uses:

curl -v -X POST "YOUR_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -d '{"state":"needs_you","test":true}'

The -v flag prints the full request and response, so you can see the HTTP status code. A 200 means the request reached its destination; a 401/403 points at a bad or expired token, and a timeout or 000 means the URL is wrong or unreachable.

If that doesn't produce a notification, the problem is downstream (URL, token, or the app) — not your hooks. If it does, but Claude Code still doesn't fire it, the problem is the hook config or the shell.

4. The silent-shell trap (PATH and non-interactive shells)

Hooks run in a non-interactive shell, which doesn't load your ~/.zshrc or ~/.bashrc. If your command relies on a tool that's only on your PATH because of those files, it'll work when you test it manually and fail when the hook runs it. Use absolute paths (/usr/bin/curl) and avoid depending on shell aliases or functions inside hooks.

If you're not sure where a binary lives, run which curl (or which <tool>) in your terminal and paste the full path into your hook command. This is the single most common reason a hook "works when I test it" but never fires on its own.

5. You're using the wrong hook event

Different events fire at different moments. If you only hooked Stop, you won't hear about permission prompts mid-task. For full coverage you generally want:

  • Stop — the agent finished and is waiting on you
  • Notification — Claude explicitly notifies you about something (including permission prompts)
  • SessionStart — a new agent came online

See the complete hooks guide for the full event list and what each one carries. If you run several agents at once, matching the right events matters even more — here's how to run multiple Claude Code agents in parallel without losing track of which one needs you.

Stop babysitting your terminal

Agentfy pushes Claude Code status to your iPhone — Live Activities, Dynamic Island, and instant alerts.

Download Agentfy

6. iOS settings (if you're using a phone app)

If the webhook is firing but your phone stays quiet, the issue is on the device:

  • Notifications enabled for the app in Settings → Notifications?
  • Is Focus / Do Not Disturb silencing it? Allow the app through your Focus filters.
  • Live Activities turned on (Settings → Face ID & Passcode → Live Activities, and per-app)?
  • Token still valid? If you regenerated it, re-run setup so your hooks use the new URL.
Info

Live Activities and the Dynamic Island have their own toggles separate from standard notifications, so a banner can work while the Live Activity stays blank. If your status updates aren't showing on the Lock Screen, see Claude Code Live Activities and Dynamic Island for the exact settings to check.

Still stuck?

Work top to bottom — config, restart, isolate the webhook, check the shell, confirm the event, then the device. Ninety percent of cases are fixed by step two.

If you'd rather skip the manual debugging, Agentfy ships an open-source Claude Code plugin that installs the hooks correctly for you (with a ready-made setup prompt as a fallback on older Claude Code), plus a built-in "Having Issues" flow that regenerates your token and re-tests the connection. New here? Start with getting Claude Code notifications on your iPhone.

About the author

Ian Brillantes · Founder & iOS Engineer

Ian is the founder of Agentfy and a senior software engineer who lives in Claude Code daily. He builds the hooks-to-push-notification pipeline behind Agentfy and writes these guides from the same multi-agent workflow they describe.

Part of Claude Code on Your iPhone

More guides in this series

Frequently asked questions

Why aren't my Claude Code hooks firing at all?+
The most common reason is that hooks are read once when a session starts, so edits to settings.json don't apply to a session that's already running. Exit and restart Claude Code (or run /reload-plugins if you use the Agentfy plugin), then check that the hook lives in the right file — global ~/.claude/settings.json versus the project-level .claude/settings.json that can override it — and that the JSON is valid. If you installed the plugin, its hooks live inside the plugin rather than your settings.json, so confirm it's active with 'claude plugin list'.
My curl webhook works in the terminal but not from the hook — why?+
Hooks run in a non-interactive shell that doesn't load ~/.zshrc or ~/.bashrc, so any tool, alias, or PATH entry defined there is unavailable. Use absolute paths like /usr/bin/curl in your hook command and avoid relying on shell functions or aliases. That single change fixes most works-manually-but-not-from-the-hook reports.
Why does Claude Code notify me when it finishes but not for permission prompts?+
You've likely only hooked the Stop event, which fires when the agent finishes and waits on you. To also hear about mid-task permission prompts, add the Notification event, and add SessionStart if you want to know when a new agent comes online. Each event fires at a different moment, so cover the ones you care about.
The webhook fires but my iPhone stays silent. What's wrong?+
The problem is on the device, not your hooks. Confirm notifications are enabled for the app under Settings, check that a Focus or Do Not Disturb mode isn't silencing it, and make sure Live Activities are turned on if you rely on them. If you regenerated your token, re-run setup so your hooks point at the new URL.
Do I need to restart Claude Code after editing settings.json?+
Yes. Claude Code reads hook configuration when a session starts and keeps it in memory, so any change you make to settings.json mid-session is ignored until you restart. Exit and relaunch Claude Code, then use /resume to pick your conversation back up. If you manage hooks through the Agentfy plugin, run /reload-plugins instead of restarting. This step alone resolves a large share of 'it's configured but nothing happens' cases.

Related articles

Ready to stop babysitting your terminal?

Agentfy pushes Claude Code status to your iPhone — Live Activities, Dynamic Island, and instant alerts. Set up in under a minute.

Download Agentfy