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

# Hooks

Hooks let you observe, control, and extend the agent loop using custom scripts. Hooks are spawned processes that communicate over stdio using JSON in both directions. They run before or after defined stages of the agent loop and can observe, block, or modify behavior.

<video autoPlay loop muted playsInline style={{width: '95%'}}>
  <source src="https://mintcdn.com/firebendercorp/vaDEDt0Xgnc8-4X4/assets/hooks-demo.webm?fit=max&auto=format&n=vaDEDt0Xgnc8-4X4&q=85&s=0b9da70efd0994852d3245c2b78f21c1" type="video/webm" data-path="assets/hooks-demo.webm" />
</video>

With hooks, you can:

* Run formatters after edits
* Add analytics for events
* Scan for PII or secrets
* Gate risky operations (e.g., SQL writes)

## Agent Support

Hooks work with Firebender Agent. The agent uses these hook events:

* `sessionStart` / `sessionEnd` - Observe chat session lifecycle for analytics and batching
* `subagentStart` / `subagentStop` - Observe Task-tool subagent lifecycle and add subagent-specific context
* `preToolUse` / `postToolUse` - Generic tool use hooks (fires for all tools)
* `beforeShellExecution` / `afterShellExecution` - Control shell commands
* `beforeMCPExecution` / `afterMCPExecution` - Control MCP tool usage
* `beforeReadFile` / `afterFileEdit` - Control file access and edits
* `preCompact` - Observe context window compaction
* `stop` - Handle agent completion

## Quickstart

Create a `hooks.json` file. You can create it at the project level (`<project>/.firebender/hooks.json`) or in your home directory (`~/.firebender/hooks.json`). Project-level hooks apply only to that specific project, while home directory hooks apply globally.

<Tabs>
  <Tab title="User hooks (~/.firebender/)">
    For user-level hooks that apply globally, create `~/.firebender/hooks.json`:

    ```json theme={null}
    {
      "version": 1,
      "hooks": {
        "afterFileEdit": [{ "command": "./hooks/format.sh" }]
      }
    }
    ```

    Create your hook script at `~/.firebender/hooks/format.sh`:

    ```bash theme={null}
    #!/bin/bash
    # Read input, do something, exit 0
    cat > /dev/null
    exit 0
    ```

    Make it executable:

    ```bash theme={null}
    chmod +x ~/.firebender/hooks/format.sh
    ```

    Restart your IDE. Your hook now runs after every file edit.
  </Tab>

  <Tab title="Project hooks (.firebender/)">
    For project-level hooks that apply to a specific repository, create `<project>/.firebender/hooks.json`:

    ```json theme={null}
    {
      "version": 1,
      "hooks": {
        "afterFileEdit": [{ "command": ".firebender/hooks/format.sh" }]
      }
    }
    ```

    Note: Project hooks run from the project root, so use `.firebender/hooks/format.sh` (not `./hooks/format.sh`).

    Create your hook script at `<project>/.firebender/hooks/format.sh`:

    ```bash theme={null}
    #!/bin/bash
    # Read input, do something, exit 0
    cat > /dev/null
    exit 0
    ```

    Make it executable:

    ```bash theme={null}
    chmod +x .firebender/hooks/format.sh
    ```

    Restart your IDE. Your hook now runs after every file edit.
  </Tab>
</Tabs>

## Hook Types

Hooks support command-based hooks.

### Command-Based Hooks

Command hooks execute shell scripts that receive JSON input via stdin and return JSON output via stdout.

```json theme={null}
{
  "hooks": {
    "beforeShellExecution": [
      {
        "command": "./scripts/approve-network.sh",
        "timeout": 30,
        "matcher": "curl|wget|nc"
      }
    ]
  }
}
```

Exit code behavior:

* **Exit code 0** - Hook succeeded, use the JSON output
* **Exit code 2** - Block the action (equivalent to returning `permission: "deny"`)
* **Other exit codes** - Hook failed, action proceeds (fail-open by default)

## Configuration

### Configuration File

This example shows a user-level hooks file (`~/.firebender/hooks.json`). For project-level hooks, change paths like `./hooks/script.sh` to `.firebender/hooks/script.sh`:

```json theme={null}
{
  "version": 1,
  "hooks": {
    "sessionStart": [{ "command": "./hooks/session-start.sh" }],
    "sessionEnd": [{ "command": "./hooks/session-end.sh" }],
    "subagentStart": [{ "command": "./hooks/subagent-start.sh" }],
    "subagentStop": [{ "command": "./hooks/subagent-stop.sh" }],
    "preToolUse": [
      {
        "command": "./hooks/validate-tool.sh",
        "matcher": "Shell|Read|Write"
      }
    ],
    "postToolUse": [{ "command": "./hooks/audit-tool.sh" }],
    "beforeShellExecution": [{ "command": "./scripts/approve-network.sh" }],
    "afterShellExecution": [{ "command": "./hooks/audit.sh" }],
    "beforeMCPExecution": [{ "command": "./hooks/audit.sh" }],
    "afterMCPExecution": [{ "command": "./hooks/audit.sh" }],
    "beforeReadFile": [{ "command": "./hooks/redact-secrets.sh" }],
    "afterFileEdit": [{ "command": "./format.sh" }],
    "preCompact": [{ "command": "./hooks/audit.sh" }],
    "stop": [{ "command": "./hooks/audit.sh" }]
  }
}
```

### Global Configuration Options

| Option  | Type   | Default | Description           |
| ------- | ------ | ------- | --------------------- |
| version | number | 1       | Config schema version |

### Per-Script Configuration Options

| Option      | Type                  | Default          | Description                                                |
| ----------- | --------------------- | ---------------- | ---------------------------------------------------------- |
| command     | string                | required         | Script path or command                                     |
| type        | "command" \| "prompt" | "command"        | Hook execution type                                        |
| timeout     | number                | platform default | Execution timeout in seconds                               |
| loop\_limit | number \| null        | 5                | Per-script loop limit for stop hooks. null means no limit. |
| matcher     | object                | -                | Filter criteria for when hook runs                         |

### Matcher Configuration

Matchers let you filter when a hook runs. Which field the matcher applies to depends on the hook:

```json theme={null}
{
  "hooks": {
    "preToolUse": [
      {
        "command": "./validate-shell.sh",
        "matcher": "Shell"
      }
    ],
    "beforeShellExecution": [
      {
        "command": "./approve-network.sh",
        "matcher": "curl|wget|nc "
      }
    ]
  }
}
```

`beforeShellExecution`: The matcher runs against the shell command string. Use it to run hooks only when the command matches a pattern (e.g. network calls, file deletions). The example above runs `approve-network.sh` only when the command contains `curl`, `wget`, or `nc `.

**Available matchers by hook:**

* `preToolUse` (and other tool hooks): Filter by tool type — Shell, Read, Write, Grep, Delete, MCP, Task, etc.
* `beforeShellExecution`: Filter by the shell command text; the matcher is matched against the full command string.

## Reference

### Common Schema

#### Input (all hooks)

All hooks receive a base set of fields in addition to their hook-specific fields:

```json theme={null}
{
  "conversation_id": "string",
  "generation_id": "string",
  "model": "string",
  "hook_event_name": "string",
  "firebender_version": "string",
  "workspace_roots": ["<path>"],
  "user_email": "string | null",
  "transcript_path": "string | null"
}
```

| Field               | Type           | Description                                                                                               |
| ------------------- | -------------- | --------------------------------------------------------------------------------------------------------- |
| conversation\_id    | string         | Stable ID of the conversation across many turns                                                           |
| generation\_id      | string         | The current generation that changes with every user message                                               |
| model               | string         | The model configured for the composer that triggered the hook                                             |
| hook\_event\_name   | string         | Which hook is being run                                                                                   |
| firebender\_version | string         | Firebender plugin version (e.g., "1.7.2")                                                                 |
| workspace\_roots    | string\[]      | The list of root folders in the workspace (normally just one, but multiroot workspaces can have multiple) |
| user\_email         | string \| null | Email address of the authenticated user, if available                                                     |
| transcript\_path    | string \| null | Path to the main conversation transcript file (null if transcripts disabled)                              |

### Hook Events

#### sessionStart

Called when a chat session becomes active. Useful for session analytics, per-session bookkeeping, and initializing any per-conversation state you want to aggregate later.

```json theme={null}
// Input
{
  "conversation_id": "abc123",
  "session_id": "abc123",
  "model": "gpt-5.4-mini",
  "hook_event_name": "sessionStart",
  "workspace_roots": ["/project"],
  "user_email": "user@example.com",
  "firebender_version": "0.15.20",
  "is_background_agent": false,
  "composer_mode": "write"
}
```

| Input Field           | Type              | Description                                                          |
| --------------------- | ----------------- | -------------------------------------------------------------------- |
| conversation\_id      | string            | Stable identifier for the active conversation                        |
| session\_id           | string            | Same session identifier, provided for compatibility with other tools |
| is\_background\_agent | boolean           | Whether the session belongs to a background agent                    |
| composer\_mode        | string (optional) | The current composer mode, if available                              |

#### sessionEnd

Called when a chat session ends. Useful for batching or summarizing per-session activity such as skill usage, tool usage, or audit records.

```json theme={null}
// Input
{
  "conversation_id": "abc123",
  "session_id": "abc123",
  "model": "gpt-5.4-mini",
  "hook_event_name": "sessionEnd",
  "workspace_roots": ["/project"],
  "user_email": "user@example.com",
  "firebender_version": "0.15.20",
  "reason": "user_close",
  "duration_ms": 5432,
  "is_background_agent": false,
  "final_status": "completed"
}
```

| Input Field           | Type              | Description                                                          |
| --------------------- | ----------------- | -------------------------------------------------------------------- |
| conversation\_id      | string            | Stable identifier for the active conversation                        |
| session\_id           | string            | Same session identifier, provided for compatibility with other tools |
| reason                | string            | Why the session ended, such as `user_close`                          |
| duration\_ms          | number            | Session duration in milliseconds                                     |
| is\_background\_agent | boolean           | Whether the session belonged to a background agent                   |
| final\_status         | string            | Terminal state such as `completed`, `aborted`, or `error`            |
| error\_message        | string (optional) | Present when the session ended with an error                         |

#### subagentStart

Called when the `Task` tool spawns a subagent. Useful for subagent analytics, auditing by agent type, or injecting additional context into the subagent before it begins.

```json theme={null}
// Input
{
  "conversation_id": "abc123",
  "session_id": "abc123",
  "model": "gpt-5.4-mini",
  "hook_event_name": "subagentStart",
  "workspace_roots": ["/project"],
  "user_email": "user@example.com",
  "firebender_version": "0.15.20",
  "agent_id": "agent-abc123",
  "agent_type": "Explore",
  "transcript_path": "/Users/.../.firebender/projects/.../transcripts/agent-abc123.jsonl",
  "cwd": "/project"
}

// Output
{
  "hookSpecificOutput": {
    "additionalContext": "Follow security guidelines for this task"
  }
}
```

| Input Field      | Type              | Description                                   |
| ---------------- | ----------------- | --------------------------------------------- |
| agent\_id        | string            | Unique identifier for the spawned subagent    |
| agent\_type      | string            | Subagent type name used for matcher filtering |
| transcript\_path | string (optional) | Path to the subagent transcript file          |
| cwd              | string (optional) | Project working directory for the subagent    |

| Output Field                         | Type              | Description                                        |
| ------------------------------------ | ----------------- | -------------------------------------------------- |
| hookSpecificOutput.additionalContext | string (optional) | Additional context appended to the subagent prompt |

#### subagentStop

Called when a `Task`-tool subagent finishes. Useful for per-subagent analytics, summaries, or cleanup keyed by subagent type.

```json theme={null}
// Input
{
  "conversation_id": "abc123",
  "session_id": "abc123",
  "model": "gpt-5.4-mini",
  "hook_event_name": "subagentStop",
  "workspace_roots": ["/project"],
  "user_email": "user@example.com",
  "firebender_version": "0.15.20",
  "agent_id": "agent-abc123",
  "agent_type": "Explore",
  "transcript_path": "/Users/.../.firebender/projects/.../transcripts/agent-abc123.jsonl",
  "agent_transcript_path": "/Users/.../.firebender/projects/.../transcripts/agent-abc123.jsonl",
  "cwd": "/project",
  "stop_hook_active": false,
  "status": "completed",
  "last_assistant_message": "Analysis complete. Found 3 potential issues..."
}

// Output
{
  "followup_message": "<optional future use>"
}
```

| Input Field              | Type              | Description                                                |
| ------------------------ | ----------------- | ---------------------------------------------------------- |
| agent\_id                | string            | Unique identifier for the subagent                         |
| agent\_type              | string            | Subagent type name used for matcher filtering              |
| transcript\_path         | string (optional) | Path to the subagent transcript file                       |
| agent\_transcript\_path  | string (optional) | Same subagent transcript path, provided for compatibility  |
| cwd                      | string (optional) | Project working directory for the subagent                 |
| stop\_hook\_active       | boolean           | Reserved for stop-style hook compatibility                 |
| status                   | string            | Terminal state such as `completed`, `aborted`, or `error`  |
| last\_assistant\_message | string (optional) | Final assistant text emitted by the subagent, if available |

#### preToolUse

Called before any tool execution. This is a generic hook that fires for all tool types (Shell, Read, Write, MCP, Task, etc.). Use matchers to filter by specific tools.

```json theme={null}
// Input
{
  "tool_name": "Shell",
  "tool_input": { "command": "npm install", "working_directory": "/project" },
  "tool_use_id": "abc123",
  "cwd": "/project",
  "model": "claude-sonnet-4-20250514",
  "agent_message": "Installing dependencies..."
}

// Output
{
  "decision": "allow" | "deny",
  "reason": "<reason shown to agent if denied>",
  "updated_input": { "command": "npm ci" }
}
```

| Output Field   | Type              | Description                            |
| -------------- | ----------------- | -------------------------------------- |
| decision       | string            | "allow" to proceed, "deny" to block    |
| reason         | string (optional) | Explanation shown to agent when denied |
| updated\_input | object (optional) | Modified tool input to use instead     |

#### postToolUse

Called after successful tool execution. Useful for auditing and analytics.

```json theme={null}
// Input
{
  "tool_name": "Shell",
  "tool_input": { "command": "npm test" },
  "tool_output": "All tests passed",
  "tool_use_id": "abc123",
  "cwd": "/project",
  "duration": 5432,
  "model": "claude-sonnet-4-20250514"
}

// Output
{
  "updated_mcp_tool_output": { "modified": "output" }
}
```

| Input Field  | Type   | Description                    |
| ------------ | ------ | ------------------------------ |
| duration     | number | Execution time in milliseconds |
| tool\_output | string | Full output from the tool      |

| Output Field               | Type              | Description                                                    |
| -------------------------- | ----------------- | -------------------------------------------------------------- |
| updated\_mcp\_tool\_output | object (optional) | For MCP tools only: replaces the tool output seen by the model |

#### beforeShellExecution / beforeMCPExecution

Called before any shell command or MCP tool is executed. Return a permission decision.

`beforeMCPExecution` uses fail-closed behavior. If the hook script fails to execute (crashes, times out, or returns invalid JSON), the MCP tool call will be blocked. This ensures MCP operations cannot bypass configured hooks.

```json theme={null}
// beforeShellExecution input
{
  "command": "<full terminal command>",
  "cwd": "<current working directory>",
  "timeout": 30
}

// beforeMCPExecution input
{
  "tool_name": "<tool name>",
  "tool_input": "<json params>"
}
// Plus either:
{ "url": "<server url>" }
// Or:
{ "command": "<command string>" }

// Output
{
  "permission": "allow" | "deny" | "ask",
  "user_message": "<message shown in client>",
  "agent_message": "<message sent to agent>"
}
```

#### afterShellExecution

Fires after a shell command executes; useful for auditing or collecting metrics from command output.

```json theme={null}
// Input
{
  "command": "<full terminal command>",
  "output": "<full terminal output>",
  "duration": 1234
}
```

| Field    | Type   | Description                                                                              |
| -------- | ------ | ---------------------------------------------------------------------------------------- |
| command  | string | The full terminal command that was executed                                              |
| output   | string | Full output captured from the terminal                                                   |
| duration | number | Duration in milliseconds spent executing the shell command (excludes approval wait time) |

#### afterMCPExecution

Fires after an MCP tool executes; includes the tool's input parameters and full JSON result.

```json theme={null}
// Input
{
  "tool_name": "<tool name>",
  "tool_input": "<json params>",
  "result_json": "<tool result json>",
  "duration": 1234
}
```

| Field        | Type   | Description                                                                         |
| ------------ | ------ | ----------------------------------------------------------------------------------- |
| tool\_name   | string | Name of the MCP tool that was executed                                              |
| tool\_input  | string | JSON params string passed to the tool                                               |
| result\_json | string | JSON string of the tool response                                                    |
| duration     | number | Duration in milliseconds spent executing the MCP tool (excludes approval wait time) |

#### afterFileEdit

Fires after the Agent edits a file; useful for formatters or accounting of agent-written code.

```json theme={null}
// Input
{
  "file_path": "<absolute path>",
  "edits": [{ "old_string": "<search>", "new_string": "<replace>" }]
}
```

#### beforeReadFile

Called before Agent reads a file. Use for access control to block sensitive files from being sent to the model.

This hook uses fail-closed behavior. If the hook script fails to execute (crashes, times out, or returns invalid JSON), the file read will be blocked. This provides security guarantees for sensitive file access.

```json theme={null}
// Input
{
  "file_path": "<absolute path>",
  "content": "<file contents>",
  "attachments": [
    {
      "type": "file" | "rule",
      "filePath": "<absolute path>"
    }
  ]
}

// Output
{
  "permission": "allow" | "deny",
  "user_message": "<message shown when denied>"
}
```

| Input Field | Type   | Description                                    |
| ----------- | ------ | ---------------------------------------------- |
| file\_path  | string | Absolute path to the file being read           |
| content     | string | Full contents of the file                      |
| attachments | array  | Context attachments associated with the prompt |

| Output Field  | Type              | Description                         |
| ------------- | ----------------- | ----------------------------------- |
| permission    | string            | "allow" to proceed, "deny" to block |
| user\_message | string (optional) | Message shown to user when denied   |

#### preCompact

Called before context window compaction/summarization occurs. This is an observational hook that cannot block or modify the compaction behavior. Useful for logging when compaction happens or notifying users.

```json theme={null}
// Input
{
  "trigger": "auto" | "manual",
  "context_usage_percent": 85,
  "context_tokens": 120000,
  "context_window_size": 128000,
  "message_count": 45,
  "messages_to_compact": 30,
  "is_first_compaction": true | false
}

// Output
{
  "user_message": "<message to show when compaction occurs>"
}
```

| Input Field             | Type    | Description                                                |
| ----------------------- | ------- | ---------------------------------------------------------- |
| trigger                 | string  | What triggered the compaction: "auto" or "manual"          |
| context\_usage\_percent | number  | Current context window usage as a percentage (0-100)       |
| context\_tokens         | number  | Current context window token count                         |
| context\_window\_size   | number  | Maximum context window size in tokens                      |
| message\_count          | number  | Number of messages in the conversation                     |
| messages\_to\_compact   | number  | Number of messages that will be summarized                 |
| is\_first\_compaction   | boolean | Whether this is the first compaction for this conversation |

| Output Field  | Type              | Description                                        |
| ------------- | ----------------- | -------------------------------------------------- |
| user\_message | string (optional) | Message to show to the user when compaction occurs |

#### stop

Called when the agent loop ends. Can optionally auto-submit a follow-up user message to keep iterating.

```json theme={null}
// Input
{
  "status": "completed" | "aborted" | "error",
  "loop_count": 0
}

// Output
{
  "followup_message": "<message text>"
}
```

The optional `followup_message` is a string. When provided and non-empty, Firebender will automatically submit it as the next user message. This enables loop-style flows (e.g., iterate until a goal is met).

The `loop_count` field indicates how many times the stop hook has already triggered an automatic follow-up for this conversation (starts at 0). To prevent infinite loops, a maximum of 5 auto follow-ups is enforced.

### Environment Variables

Hook scripts receive environment variables when executed:

| Variable                 | Description               | Always Present |
| ------------------------ | ------------------------- | -------------- |
| FIREBENDER\_PROJECT\_DIR | Workspace root directory  | Yes            |
| FIREBENDER\_VERSION      | Firebender version string | Yes            |
| FIREBENDER\_USER\_EMAIL  | Authenticated user email  | If logged in   |

## Troubleshooting

### How to confirm hooks are active

There is a Hooks tab in Firebender Settings to debug configured and executed hooks, as well as the Hooks output in `~/.firebender/hooks-logs` to see errors.

### If hooks are not working

1. Restart your IDE to ensure the hooks service is running.
2. Check that relative paths are correct for your hook source:
   * For project hooks, paths are relative to the project root (e.g., `.firebender/hooks/script.sh`)
   * For user hooks, paths are relative to `~/.firebender/` (e.g., `./hooks/script.sh` or `hooks/script.sh`)

### Exit code blocking

Exit code 2 from command hooks blocks the action (equivalent to returning `decision: "deny"`).
