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.
{
"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_IDandNEURALINVERSE_TOOL_NAME.Exit 0 means success. Standard output may be a JSON object:
{"decision": "allow", "reason": "...", "additionalContext": "...", "updatedInput": {}}decisionis one ofallow,deny,blockorask. Forprompt.submitandsession.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. 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 applies an organisation-specific command policy that users cannot remove:
{ "hooks": { "tool.before": [{ "matcher": "run_shell", "command": "/opt/corp/ni-command-policy" }] } }
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.