articleJanuary 3, 2026

Get Notified When Claude Code Finishes With Hooks

Set up desktop notifications for Claude Code using hooks to get alerted when Claude needs permission or input—no more terminal watching.

Hooks are commands that run at specific points in Claude Code's lifecycle. Instead of polling the terminal, you get notified when something needs your attention.

Claude Code provides two notification hooks:

  • permission_prompt — Claude needs your permission to do something
  • idle_prompt — Claude is waiting for your input

Setup

Create a .claude/hooks directory in your project, then add the hook configuration to .claude/settings.json:

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt|idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "npx tsx \"$CLAUDE_PROJECT_DIR/.claude/hooks/notification-desktop.ts\"",
            "timeout": 5
          }
        ]
      }
    ]
  }
}

The $CLAUDE_PROJECT_DIR environment variable expands to your project root automatically.

Placement options:

  • .claude/settings.json — Project-specific (shared with team)
  • ~/.claude/settings.json — Global user settings (personal only)

Notification Script

The script reads hook input from stdin and sends macOS notifications via AppleScript:

import type { NotificationHookInput } from '@anthropic-ai/claude-agent-sdk'
import { execSync } from 'node:child_process'
import { readFileSync } from 'node:fs'

function sendMacNotification(title: string, message: string): void {
  const escapedTitle = title.replace(/"/g, '\\"')
  const escapedMessage = message.replace(/"/g, '\\"')
  const script = `display notification "${escapedMessage}" with title "${escapedTitle}" sound name "Ping"`
  execSync(`osascript -e '${script}'`, { stdio: 'ignore' })
}

function main(): void {
  const input = JSON.parse(readFileSync(0, 'utf-8')) as NotificationHookInput
  const notificationType = (input as { notification_type?: string }).notification_type

  if (notificationType === 'permission_prompt') {
    sendMacNotification('Claude Code - Permission Required', input.message || 'Claude needs your permission')
  } else if (notificationType === 'idle_prompt') {
    sendMacNotification('Claude Code - Waiting', input.message || 'Claude is waiting for your input')
  }
}

main()

Key Benefit

The notification arrives exactly when you need to engage—no sooner, no later. You reclaim focus by letting Claude work while you work, with attention pulled back only when needed.

See claude-code-best-practices for more Claude Code optimization techniques.

Connections (23)