---
title: "Hooks"
---

# Hooks

Run your own shell commands at points in the CLI agent's lifecycle to block actions, add context or automate follow-ups, with the events, payloads and protocol.

Hooks are shell commands that run at points in the agent's lifecycle. Use them to enforce a policy (block a command), add context (inject ticket details) or automate a follow-up (format after edits). They are set in the `hooks` key of any settings file; `/hooks` lists the hooks in effect and where each is set.

```jsonc
{
  "hooks": {
    "tool.before": [{ "matcher": "run_shell", "command": "./scripts/check-command.sh" }],
    "tool.after":  [{ "matcher": "edit_file|write_file|multi_edit", "command": "npx prettier --write \"$(jq -r .tool_input.path)\"" }],
    "agent.stop":  [{ "command": "npm test --silent >/dev/null 2>&1 || { echo 'tests are failing' >&2; exit 2; }" }]
  }
}
```

## Events

| Event | When | Can block? | Payload fields besides the common ones |
|---|---|---|---|
| `session.start` | The session begins | No | `source` |
| `session.end` | The session ends | No | |
| `prompt.submit` | Before your prompt reaches the model | Yes: the prompt is rejected | `prompt` |
| `tool.before` | Before a tool runs, after input validation | Yes: the call is refused | `tool_name`, `tool_input`, `tool_call_id` |
| `tool.after` | After a tool returns | Feedback only | `tool_name`, `tool_input`, `tool_call_id`, `tool_result`, `is_error` |
| `agent.stop` | The agent is about to finish its answer | Yes: the agent continues with your reason | `last_message` |
| `compact.before` | Before history is summarised | Yes, for automatic compaction only | `trigger` |
| `notify` | The agent needs your attention | No | |

Common fields: `hook_event_name`, `session_id`, `cwd`.

`matcher` applies to tool events only. It is a tool-name pattern such as `run_shell`, `edit_file|write_file` or `mcp_*`. No matcher means every tool.

## Protocol

- **Input.** The payload is JSON on standard input. The environment has `NEURALINVERSE_HOOK_EVENT`, `NEURALINVERSE_PROJECT_DIR`, `NEURALINVERSE_SESSION_ID` and `NEURALINVERSE_TOOL_NAME`.
- **Exit 0** means success. Standard output may be a JSON object:

  ```json
  {"decision": "allow", "reason": "...", "additionalContext": "...", "updatedInput": {}}
  ```

  `decision` is one of `allow`, `deny`, `block` or `ask`. For `prompt.submit` and `session.start`, plain-text output is added to the model's context.
- **Exit 2** blocks. Standard error is the reason, shown to the model where it applies.
- **Any other exit code** means the hook failed: a warning is shown and the action proceeds.

`updatedInput` (in `tool.before`) replaces the tool input; it is validated again before use. `decision: "allow"` skips the approval prompt but never overrides deny rules. Hooks for the same event run in parallel, each with a 60-second default timeout (`timeoutMs` per hook).

## Hooks and organisation governance

Hooks run under [organisation governance](https://neuralinverse.com/docs/cli/permissions/#organisation-governance-comes-first). Before a hook command starts, it is decided like a command the agent runs. A hook that governance does not allow does not run; for `tool.before` and `prompt.submit`, the action it guards is then blocked. A hook's `allow` never skips an approval governance asks for, and a rewritten `updatedInput` is decided like any other input.

## Organisation hooks

A `tool.before` hook in the [managed settings](https://neuralinverse.com/docs/cli/enterprise/) applies an organisation-specific command policy that users cannot remove:

```jsonc
{ "hooks": { "tool.before": [{ "matcher": "run_shell", "command": "/opt/corp/ni-command-policy" }] } }
```

[Plugins](https://neuralinverse.com/docs/cli/plugins/) can also bring hooks; they run alongside your own. A hook runs with your permissions, so review hooks from any source before you use them.
