> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quadrillion.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks

> Run your own commands automatically before agent tools, after them, or when the agent finishes.

Hooks are shell commands that Qualia runs at fixed points in an agent's turn. Use them to check a command before it runs, format files after the agent edits them, or run your tests before the agent says it is done.

Open them from the gear menu in the titlebar: **Customize agent** > **Hooks**. **Getting started** explains the flow and offers three templates that need only `python3`, so they work on desktop and on Qualia Cloud: block `rm -rf` in shell commands, log shell commands and file edits to `.quadrillion/hook-log.jsonl`, and check Python syntax before the agent finishes. Using a template adds its hooks to **Workspace hooks** as an unsaved draft and keeps the hooks already in the file. The **Templates** menu in the editor adds the same ones to the file you are editing. If the file is not valid JSON, Qualia asks before replacing it with the template.

To format code or run your tests, see [Examples](#examples).

## Workspace and personal hooks

* **Workspace hooks** are stored with the project in `.quadrillion/hooks.json`, so collaborators get the same file.
* **Personal hooks** apply across all your workspaces. On desktop they live in Qualia's configuration directory; on Qualia Cloud they are stored with your account.

All hooks that match an event run at the same time.

## Review before hooks run

A hook file does nothing until you enable it. After saving, click **Enable reviewed hooks**. Qualia remembers the exact version you enabled, separately for each person.

If the file changes afterwards, for example because a collaborator edited it, its hooks stop running until you review and enable the new version. The agent notes in chat when hooks were skipped for this reason, and also warns in chat when a hook file you had enabled has been deleted.

If `hooks.json` changes on disk while you are editing it, **Save** fails and asks you to reload. The error offers **Copy edits** so you can keep your draft; **Reload** asks before it discards unsaved edits.

<Warning>
  Enabling covers `hooks.json` only, not the scripts it calls. Review those scripts too, and keep them somewhere you control.
</Warning>

## Configuration

```json theme={null}
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "python3 .quadrillion/hooks/check_command.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
```

Each event holds a list of groups. Each group has an optional `matcher` and one or more commands.

| Event | Runs |
| - | - |
| `PreToolUse` | Before a tool runs. Can block it. |
| `PostToolUse` | After a tool succeeds. |
| `PostToolUseFailure` | After a tool fails. |
| `Stop` | When the agent is about to finish. Can ask it to keep working. |

Command options:

| Option | Default | Meaning |
| - | - | - |
| `command` | required | Shell command to run from the workspace folder. |
| `timeout` | `10` | Seconds before the command is stopped (1–120). |
| `loop_limit` | `5` | `Stop` only: how many times this hook can send the agent back to work in one turn (1–5). After that, the agent is allowed to finish. |
| `fail_closed` | `false` | `PreToolUse` only: block the tool if this command fails, times out, or returns output Qualia cannot read. |

A file can hold up to 32 commands.

### Matchers

A matcher selects tools by name. Separate alternatives with `|` and use `*` as a wildcard. An empty matcher, `*`, or `.*` matches every tool. The agent's replies to you are not tool calls, so no matcher selects them and they always stream; use a `Stop` hook to act when the agent finishes.

You can use Qualia's tool names (such as `bash_command` or `edit_cell`) or these common names: `Bash`/`Shell` for shell commands, `Read`, `Write`, and `Edit` for file tools, and `mcp__<server>__<tool>` for MCP tools. `Stop` groups take no matcher.

<Note>
  While a `PreToolUse` hook matches a tool, that tool's output does not stream live. It appears once the hook has allowed it, so the hook runs before anything changes. A `PreToolUse` group that matches every tool (an empty matcher, `*`, or `.*`) holds back every cell, edit, and write until the hook approves it, and the Hooks editor shows a note when your file has one. Narrow the matcher to keep live streaming for the other tools.
</Note>

## What your command receives

Each command gets a JSON object on standard input with `hook_event_name`, `session_id`, `cwd`, `tool_name`, `tool_input`, and, after a tool runs, `tool_response`. Shell and file tools also include `tool_input.command` or `tool_input.file_path`.

`tool_name` uses the common name when a tool has one (`Bash`, `Read`, `Write`, `Edit`, or `mcp__<server>__<tool>`), and `qualia_tool_name` always holds Qualia's own name, such as `bash_command`. Tool events also include `tool_use_id`, which identifies the call. `stop_hook_active` is `true` once a `Stop` hook has already sent the agent back to work in this turn. The environment variables `QUALIA_PROJECT_DIR` and `CLAUDE_PROJECT_DIR` hold the workspace folder. Qualia does not provide a `transcript_path`, so scripts that read the conversation transcript will not work unchanged.

The input is capped at 96 KiB. When a tool's input or response is larger, Qualia shortens its long strings and adds `"truncated": true` at the top level, so check that field before trusting the full content.

## Responding

* **Exit code 0** allows the action. Output that is a JSON object can carry a decision (see below).
* **Exit code 2** blocks a `PreToolUse` tool, or sends the agent back to work from a `Stop` hook. From `PostToolUse` or `PostToolUseFailure`, it passes the reason to the agent and keeps it working; the tool has already run. Standard error becomes the reason shown to the agent.
* **Any other exit code** counts as a hook error. Errors are reported in chat and do not stop the agent, unless the command sets `fail_closed`.

JSON responses can use these fields:

| Field | Effect |
| - | - |
| `decision: "block"` | Block the tool (`PreToolUse`), send the agent back to work (`Stop`), or pass the reason to the agent after a tool ran (`PostToolUse`, `PostToolUseFailure`). |
| `hookSpecificOutput.permissionDecision: "deny"` | `PreToolUse` only: block the tool. |
| `reason`, `hookSpecificOutput.permissionDecisionReason` | Reason shown to the agent. |
| `continue: false` with `stopReason` | Hooks never end the agent's turn. The reason is passed to the agent, and a `PreToolUse` hook also blocks the tool. |
| `hookSpecificOutput.additionalContext` | Context passed to the agent when no reason is given. |
| `systemMessage` | Extra message shown to the agent. |

Qualia cannot ask for your approval from a hook or rewrite a tool's input. A `PreToolUse` response that asks for approval (`permissionDecision: "ask"`) or returns `updatedInput` blocks the tool rather than running it unchanged. Responses follow the Claude Code format; fields Qualia does not use, including Cursor's `permission` and `followup_message`, are ignored.

## Where hooks run

On desktop, hooks run on your computer inside the same sandbox as the agent's shell commands when sandboxing is on (**Settings** > **Agents** > **Sandboxing**). On Qualia Cloud, hooks run in your workspace's cloud sandbox.

Hooks can use the same programs as the agent's shell commands. On desktop, that is what your shell finds on its `PATH`. On Qualia Cloud, the sandbox has `python3` and common shell tools, but not project tools such as `pytest` or `ruff`. Install them in the sandbox, or point the hook at a copy inside your project, such as `.venv/bin/pytest`.

Web search and page fetches run as the `web_search` and `web_fetch` tools, so hooks can match them like any other tool.

## Examples

These hooks call tools that Qualia does not install, so install `ruff` or `pytest` where hooks run first (see [Where hooks run](#where-hooks-run)). If the tool is missing, each hook reports a hook error that names it and lets the agent carry on.

Format Python files with `ruff` after the agent edits them:

```json theme={null}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 -c \"import json, shutil, subprocess, sys; path = json.load(sys.stdin)['tool_input'].get('file_path') or ''; path.endswith('.py') or sys.exit(0); shutil.which('ruff') or sys.exit('ruff is not installed where hooks run, so ' + path + ' was not formatted.'); subprocess.run(['ruff', 'format', path], check=False)\"",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
```

Run `pytest` before the agent finishes, and send it back with the last lines of the test output when tests fail. A project with no tests passes.

```json theme={null}
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "command -v pytest > /dev/null || { echo 'pytest is not installed where hooks run, so the tests were not checked.' >&2; exit 1; }; output=$(pytest -q -x 2>&1); code=$?; [ $code -eq 0 ] || [ $code -eq 5 ] || { echo 'Tests are failing. Fix them before finishing.' >&2; printf '%s\\n' \"$output\" | tail -n 20 >&2; exit 2; }",
            "timeout": 120
          }
        ]
      }
    ]
  }
}
```

## Importing from Claude Code or Codex

Click **Import from Claude Code** or **Import from Codex** to copy supported command hooks into the hooks you are editing. Workspace hooks read the project's `.claude/settings.json`, `.claude/settings.local.json`, `.codex/hooks.json`, or `.codex/config.toml`. Personal hooks read only files in your home folder, and only on desktop: `~/.claude/settings.json`, `~/.codex/hooks.json`, and `~/.codex/config.toml`. `~/.claude/settings.local.json` is not read.

Each source shows a preview first. Click **Add hook(s) to draft**, then save, review, and enable the result as usual. A file holds at most 32 commands, so when your draft is nearly full the preview says how many fit and the rest are skipped. Imports only copy commands; the scripts they call are not copied, and later changes in Claude Code or Codex are not synced.

Unsupported events, options, and tools are skipped and listed in the preview. For example, `MultiEdit` in `Write|Edit|MultiEdit` is dropped and the rest of the matcher is kept. Claude Code tool names with a differently named Qualia tool are renamed: `Grep` to `ripgrep`, `Glob` to `list`, `WebFetch` to `web_fetch`, `WebSearch` to `web_search`, and `NotebookEdit` to `edit_cell|append_cell`. These tools take different input fields, so check scripts that read `tool_input`. Commands without a timeout, or with one above 120 seconds, get 120 seconds.

The preview also lists the response fields Qualia honors (see [Responding](#responding)). Before enabling imported `PreToolUse` guards, check whether their scripts ask for approval or return `updatedInput`; Qualia blocks the tool in both cases.

## Testing

With a hook file enabled, open **Test a hook** below the editor and click **Run test** to send a sample event, such as a `PreToolUse` event for `bash_command`. Test runs execute your hook commands for real but never run the agent tool. Each result shows why a hook failed, such as a timeout, a nonzero exit status, or a response Qualia could not read.
