Skip to main content
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. 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:
  • 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.
For user-level hooks that apply globally, create ~/.firebender/hooks.json:
{
  "version": 1,
  "hooks": {
    "afterFileEdit": [{ "command": "./hooks/format.sh" }]
  }
}
Create your hook script at ~/.firebender/hooks/format.sh:
#!/bin/bash
# Read input, do something, exit 0
cat > /dev/null
exit 0
Make it executable:
chmod +x ~/.firebender/hooks/format.sh
Restart your IDE. Your hook now runs after every file edit.

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.
{
  "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:
{
  "version": 1,
  "hooks": {
    "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

OptionTypeDefaultDescription
versionnumber1Config schema version

Per-Script Configuration Options

OptionTypeDefaultDescription
commandstringrequiredScript path or command
type”command” | “prompt""command”Hook execution type
timeoutnumberplatform defaultExecution timeout in seconds
loop_limitnumber | null5Per-script loop limit for stop hooks. null means no limit.
matcherobject-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:
{
  "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:
{
  "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"
}
FieldTypeDescription
conversation_idstringStable ID of the conversation across many turns
generation_idstringThe current generation that changes with every user message
modelstringThe model configured for the composer that triggered the hook
hook_event_namestringWhich hook is being run
firebender_versionstringFirebender plugin version (e.g., “1.7.2”)
workspace_rootsstring[]The list of root folders in the workspace (normally just one, but multiroot workspaces can have multiple)
user_emailstring | nullEmail address of the authenticated user, if available
transcript_pathstring | nullPath to the main conversation transcript file (null if transcripts disabled)

Hook Events

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.
// 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 FieldTypeDescription
decisionstring”allow” to proceed, “deny” to block
reasonstring (optional)Explanation shown to agent when denied
updated_inputobject (optional)Modified tool input to use instead

postToolUse

Called after successful tool execution. Useful for auditing and analytics.
// 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 FieldTypeDescription
durationnumberExecution time in milliseconds
tool_outputstringFull output from the tool
Output FieldTypeDescription
updated_mcp_tool_outputobject (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.
// 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.
// Input
{
  "command": "<full terminal command>",
  "output": "<full terminal output>",
  "duration": 1234
}
FieldTypeDescription
commandstringThe full terminal command that was executed
outputstringFull output captured from the terminal
durationnumberDuration 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.
// Input
{
  "tool_name": "<tool name>",
  "tool_input": "<json params>",
  "result_json": "<tool result json>",
  "duration": 1234
}
FieldTypeDescription
tool_namestringName of the MCP tool that was executed
tool_inputstringJSON params string passed to the tool
result_jsonstringJSON string of the tool response
durationnumberDuration 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.
// 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.
// 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 FieldTypeDescription
file_pathstringAbsolute path to the file being read
contentstringFull contents of the file
attachmentsarrayContext attachments associated with the prompt
Output FieldTypeDescription
permissionstring”allow” to proceed, “deny” to block
user_messagestring (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.
// 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 FieldTypeDescription
triggerstringWhat triggered the compaction: “auto” or “manual”
context_usage_percentnumberCurrent context window usage as a percentage (0-100)
context_tokensnumberCurrent context window token count
context_window_sizenumberMaximum context window size in tokens
message_countnumberNumber of messages in the conversation
messages_to_compactnumberNumber of messages that will be summarized
is_first_compactionbooleanWhether this is the first compaction for this conversation
Output FieldTypeDescription
user_messagestring (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.
// 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:
VariableDescriptionAlways Present
FIREBENDER_PROJECT_DIRWorkspace root directoryYes
FIREBENDER_VERSIONFirebender version stringYes
FIREBENDER_USER_EMAILAuthenticated user emailIf 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").