/tools

Hooks.

A hook is your own command that runs automatically at a fixed moment: before Nilux runs a tool, after it edits a file, when it finishes a task. Use hooks for things that must happen every time, without relying on the model to remember. For example:

  • run the code formatter after every edit;
  • refuse git push in a project where you push by hand;
  • send a message to your chat when a long task is done.

Where hooks live

Hooks are written in a JSON file:

  • ~/.nilux/hooks.json: your hooks, for every project;
  • .nilux/hooks.json in a project: hooks for that project only.

Nilux notices changes to these files by itself; there is no need to restart.

A first hook

This hook stops Nilux from running git push, and tells it why:

json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"if": "Bash(git push:*)",
"command": "echo 'In this project we push by hand.' >&2; exit 2"
}
]
}
]
}
}
  • PreToolUse is the moment: just before a tool runs.
  • matcher picks the tool: here Bash, the tool that runs commands. Use Edit|Write for several tools, or leave it empty for all.
  • if narrows it down further, here to commands that start with git push.
  • The command prints a reason and ends with exit code 2, which means block. Nilux does not run the command, and the model sees your reason.

Moments you can hook into

  • PreToolUse: before a tool runs. It can block the call, change its arguments, or make Nilux ask you first.
  • PostToolUse and PostToolUseFailure: after a tool has worked or failed.
  • PermissionRequest: when Nilux is about to ask you to approve something.
  • UserPromptSubmit: when you send a message. It can block the message or add information to it.
  • Stop and StopFailure: when Nilux finishes its turn, or the turn ends with an error.
  • Notification: when Nilux needs your attention.
  • SubagentStart and SubagentStop: when a helper agent starts or finishes.
  • PreCompact and PostCompact: around shortening a conversation.
  • SessionStart and SessionEnd: when a session begins or closes.
  • PreModelSwitch and PostModelSwitch: around a change of model.

How a hook talks back

The hook receives the details of the moment as JSON on its standard input, for example the tool's name and arguments. A script reads it like piped text, for example with jq. The hook answers with its exit code:

  • 0: all fine; Nilux continues.
  • 2: block. What the hook printed to standard error (>&2 in a shell script) is shown as the reason.
  • any other code: the hook failed. You see an error, and Nilux goes on.

A hook that guards a tool call (PreToolUse, PermissionRequest) is careful by design: if it fails, times out or answers something unreadable, the call is blocked rather than let through.

For finer control, the hook can print JSON instead, for example to allow or deny with a reason, change the tool's arguments, or add a note for the model.

Three kinds of hooks

  • command: runs a command in your shell (on Windows, in Git Bash). The project folder is available as NILUX_PROJECT_DIR.
  • http: sends the same JSON to a web address. If you list addresses in allowedHttpHookUrls in your hooks file, hooks can send only to those.
  • mcp_tool: calls a tool of one of your MCP servers.

Safety

  • A hook can approve ordinary actions, but never a dangerous command or a file deletion. If one hook allows a call and another asks, you are asked.
  • Hooks that come with a project do not run until you have seen them. Nilux shows you every command and web address they would use, and asks. If the file changes later, it asks again.

Check your hooks

/hooks shows both files, which hooks are loaded, and why any of them is not running. If something is wrong with a hook, the status line says so.