anotifier / guides / agent-hooks-explained
[ GUIDE · AGENT HOOKS ]

How agent hooks power your notifications

Every coding agent anotifier supports can run a command at fixed points in its lifecycle. Those hooks are all anotifier uses: setup adds one entry per event to each agent's own config file, the agent runs it, and the hook turns the event into a notification. No wrapper, no daemon, no polling. This page lists exactly what gets written where, and what happens when a hook fires. A hook runs with your user permissions, so see Claude Code hooks security for how to audit hooks and what anotifier's own hooks send.

Rather skip the manual setup? One command wires every agent you have installed to desktop toasts, phone push, and Slack, Discord or Telegram:

$ npx anotifier@latest setup

Updated 2026-10-12 · also see the full docs

The events, per agent

AgentConfig fileFinishedNeeds youTimeout
Claude Code~/.claude/settings.jsonStopNotification10 s
Codex CLI~/.codex/hooks.jsonStopPermissionRequest10 s
Cursor~/.cursor/hooks.jsonstopNoneNot set
Gemini CLI~/.gemini/settings.jsonAfterAgentNotification30 s

Codex also gets a SessionStart hook. Each agent event maps to one of three anotifier events: task_complete (default priority), needs_input (urgent), or session_start (low, and quiet by default). Claude Code's idle reminder ("Claude is waiting for your input") arrives as needs_input at default priority instead of urgent. The docs list every per-event setting.

What an entry looks like

Every hook runs the same script, src/notify.mjs from the installed package, with --source naming the agent. This is the Claude Code entry setup writes under both Stop and Notification:

{
  "hooks": [
    {
      "type": "command",
      "command": "node \"/path/to/anotifier/src/notify.mjs\" --source claude",
      "timeout": 10
    }
  ],
  "_managed_by": "anotifier",
  "matcher": ""
}
  • Codex and Cursor validate their files strictly, so their entries carry no _managed_by marker. Instead they pass --event <name>, because those agents don't include the event name in the hook's input. Codex entries also set statusMessage: "Sending notification".
  • Cursor uses its flat format: { "command": "node ... --source cursor --event stop" } under hooks.stop, with version: 1.
  • Gemini CLI reads hooks from settings.json, not a separate hooks file, and takes its timeout in milliseconds (30000). Google's newer Antigravity CLI, which replaced Gemini CLI for Google AI Pro and Ultra subscribers in June 2026, reads hooks from its own files (such as ~/.gemini/config/hooks.json) with different events (PreToolUse, PostToolUse, PreInvocation, PostInvocation, Stop), so Gemini CLI hooks don't carry over. anotifier 1.4.0 and later write an anotifier group with a Stop handler there.

What happens when a hook fires

  1. The agent starts notify.mjs and writes the event as JSON to its stdin. anotifier waits at most 500 ms for it.
  2. If snooze or quiet hours is active, it stops here and sends nothing. The same goes for a Claude Code Stop that fires while background tasks are still running; the real completion notifies later.
  3. Duplicates are dropped. A lock file per agent, event, and session collapses repeats within 1.5 seconds, which matters for Cursor, which can fire stop twice for one run.
  4. The notification is built: the title is my-app · Claude Code, and for Claude Code the body is the last thing Claude said (or its question), read from the transcript and capped at 180 characters. Other agents send generic text such as my-app: Task complete.
  5. Every enabled channel is sent in parallel, each with its own timeout: 5 to 7 seconds for the toast and 5 for ntfy.
  6. The hook exits 0 with the reply the agent expects. For Claude Code that reply rings the terminal bell through Claude Code's own output. Errors go to ~/.anotifier/errors.log, never into the agent session.

Codex: the feature flag and trust hashes

Current Codex releases run hooks by default; early ones needed hooks = true under [features] in ~/.codex/config.toml, so setup still sets it and migrates the older codex_hooks = true flag. Codex also runs only hooks it trusts. Each hook needs a trusted_hash under [hooks.state], keyed by the hooks file path, the event, and the entry's position, and setup writes those too.

[features]
hooks = true

Codex skips a hook without a matching hash, so if you edit one by hand it stops firing until you trust it again with /hooks in Codex. Running npx anotifier@latest setup again also rewrites the hashes. Hooks fire in the interactive Codex TUI, not in codex exec.

Backups, re-runs, and uninstall

  • Before changing a file that already exists, setup copies it to ~/.anotifier/backups/<file>.<timestamp>.backup.
  • anotifier recognises its own entries by the marker or by the notify.mjs command path, so re-running setup replaces them in place and leaves your own hooks alone.
  • anotifier uninstall asks for confirmation, backs up each file, and removes only anotifier's entries (plus any hook arrays that end up empty) and its Codex trust hashes, and restores your Claude Code statusline (1.5.0 and later). It keeps ~/.anotifier/ with your config and backups, and leaves Codex's hooks = true flag set.
  • The Claude Code plugin registers the same Stop and Notification hooks from inside the plugin. anotifier uninstall doesn't touch those; remove the plugin from Claude Code's /plugin menu.

Agent hooks: common questions

Do I need to restart the agent after setup?

Yes. Setup finishes with "Restart your AI tools to activate." A session that was already running may not pick up the new hooks.

Can a notification hook slow down or break my agent?

anotifier's hook always exits 0 and sends every channel in parallel with short per-channel timeouts, so a dead network or a missing toast tool costs seconds at most and never fails the agent's turn. Failures are written to ~/.anotifier/errors.log.

Will setup overwrite hooks I wrote myself?

No. It only replaces entries it recognises as its own, and backs up each file before writing.

Why does Cursor have no needs-input notification?

anotifier registers only Cursor's stop event, which fires when the agent loop ends. Cursor notifications are about finished runs.

Try it

One command wires every agent you have installed: Claude Code, Codex CLI, Cursor, and Gemini CLI.

npx anotifier@latest setup

Other guides