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

# Hooks

> Run shell commands on agent events. Claude Code compatible.

Hooks fire shell commands at lifecycle events — before/after tool calls, on session start/stop, around compaction. Wire-compatible with Claude Code, so your existing `.claude/settings.json` hooks work as-is.

## Quick example

Auto-format every TypeScript file the agent edits:

```json .soulforge/config.json theme={null}
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write $(echo $HOOK_TOOL_INPUT | jq -r '.path // empty')",
            "async": true
          }
        ]
      }
    ]
  }
}
```

`/hooks` inside SoulForge lists active hooks and toggles them per session.

## Events

| Event                            | Fires                  | Matches   |
| -------------------------------- | ---------------------- | --------- |
| `PreToolUse`                     | Before a tool runs     | Tool name |
| `PostToolUse`                    | After a tool succeeds  | Tool name |
| `PostToolUseFailure`             | After a tool fails     | Tool name |
| `UserPromptSubmit`               | On user message        | —         |
| `Stop` / `StopFailure`           | Turn ends              | —         |
| `SessionStart` / `SessionEnd`    | Session boundary       | —         |
| `PreCompact` / `PostCompact`     | Around compaction      | —         |
| `SubagentStart` / `SubagentStop` | Spark/ember spawn/exit | —         |
| `Notification`                   | System notifications   | —         |

## Rule schema

```json theme={null}
{
  "matcher": "Bash|Edit",       // tool name, pipe-separated or regex
  "hooks": [
    {
      "type": "command",
      "command": "my-hook.sh",
      "async": false,             // run in background
      "timeout": 10,              // seconds (default 10)
      "once": false,              // fire once per session
      "if": "Bash(git *)"        // extra glob filter
    }
  ]
}
```

The `if` field narrows by first string arg: `Bash(rm *)`, `Edit(*.ts)`, etc. `*` matches any sequence, `?` one char.

Tool names use Claude Code conventions (`Bash`, `Edit`, `Write`, `Read`, `Grep`, `Glob`, `WebSearch`, `Agent`). SoulForge maps its internal names automatically.

## Config sources (merged in order)

1. `~/.claude/settings.json`
2. `.claude/settings.json`
3. `.claude/settings.local.json`
4. `~/.soulforge/config.json`
5. `.soulforge/config.json`

Set `"disableAllHooks": true` in any file to kill all hooks.

## Recipes

<AccordionGroup>
  <Accordion title="Block `rm -rf`">
    ```json theme={null}
    {
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "echo '{\"decision\":\"block\",\"reason\":\"rm -rf blocked\"}' && exit 2",
        "if": "Bash(rm -rf *)"
      }]
    }
    ```

    Goes under `PreToolUse`. Exit code `2` denies the call.
  </Accordion>

  <Accordion title="Log every tool call">
    ```json theme={null}
    {
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_name' >> /tmp/sf-tools.log",
        "async": true
      }]
    }
    ```

    Goes under `PreToolUse`. No matcher = all tools.
  </Accordion>

  <Accordion title="Notify when done (macOS)">
    ```json theme={null}
    {
      "hooks": [{
        "type": "command",
        "command": "osascript -e 'display notification \"Done\" with title \"SoulForge\"'",
        "async": true,
        "once": true
      }]
    }
    ```

    Goes under `Stop`.
  </Accordion>
</AccordionGroup>

## Protocol

Hooks read JSON from stdin, optionally write JSON to stdout. Exit code `0` = success, `2` = block, anything else = log warning.

**stdin**

```json theme={null}
{
  "session_id": "abc",
  "cwd": "/proj",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "git status" }
}
```

**stdout** (optional — `PreToolUse` can deny, modify input, inject context)

```json theme={null}
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by policy",
    "updatedInput": { "command": "git status --short" },
    "additionalContext": "This repo uses trunk-based dev"
  }
}
```

`PostToolUse` can inject `additionalContext` — no deny.

## Safety

* Hooks die on `Ctrl+X` along with the agent.
* Default timeout 10s — hooks should be fast.
* Non-blocking errors (non-zero exit ≠ 2) log a warning, don't stop the agent.
