Hooks
Overview
Hooks let you run custom shell scripts at key moments during an Faheem Code session. They are configured per-repository
via a .faheem-code/hooks.json file and work across Cloud, CLI, and local GUI setups. The hooks format is
compatible with Claude Code hooks, so you can reuse hook scripts across both tools.
Common use cases include:
- Blocking dangerous commands before execution (e.g., preventing
rm -rf /) - Enforcing quality gates before the agent finishes (e.g., requiring linting or tests to pass)
- Logging and auditing tool usage for compliance
- Injecting context into user prompts (e.g., appending git status)
Hook types
| Hook | When It Runs | Can Block? |
|---|---|---|
PreToolUse | Before the agent executes a tool | Yes |
PostToolUse | After a tool finishes executing | No |
UserPromptSubmit | Before a user message is processed | Yes |
Stop | When the agent tries to finish | Yes |
SessionStart | When a conversation begins | No |
SessionEnd | When a conversation ends | No |
Blocking means the hook can prevent the operation from proceeding. For example, a Stop hook can force the agent
to keep working if linting checks haven't passed yet. Note that hooks with "async": true run in the background and
can never block, regardless of event type.
Quick start
Create the Hooks Directory
In your repository root, create the .faheem-code directory if it doesn't already exist:
mkdir -p .faheem-code/hooks
Write a Hook Script
Create a shell script for your hook. For example, a Stop hook that requires linting to pass before the agent can finish:
#!/bin/bash
# Stop hook: Don't let the agent stop if linting fails
cd "$FAHEEMCODE_PROJECT_DIR"
# Run your linter
if ! npm run lint 2>&1; then
echo '{"decision": "deny", "reason": "Linting failed. Please fix the issues before finishing."}'
exit 2
fi
exit 0
Make the script executable:
chmod +x .faheem-code/hooks/lint_check.sh
Create hooks.json
Create .faheem-code/hooks.json to register your hooks:
{
"stop": [
{
"matcher": "*",
"hooks": [
{
"command": ".faheem-code/hooks/lint_check.sh",
"timeout": 120
}
]
}
]
}
Commit to Your Repository
git add .faheem-code/hooks.json .faheem-code/hooks/
git commit -m "Add Faheem Code hooks"
The next time Faheem Code works on your repository, the hooks will be active automatically.
Configuration reference
hooks.json format
The .faheem-code/hooks.json file maps hook event types to matchers and commands using snake_case keys:
{
"pre_tool_use": [
{
"matcher": "terminal",
"hooks": [
{ "command": ".faheem-code/hooks/block_dangerous.sh", "timeout": 10 }
]
}
],
"stop": [
{
"matcher": "*",
"hooks": [
{ "command": ".faheem-code/hooks/require_tests.sh", "timeout": 120 }
]
}
]
}
Claude Code compatibility
The hooks format is compatible with Claude Code hooks. PascalCase event
keys (e.g., PreToolUse) and the {"hooks": {...}} wrapper are both supported, so you can share hook scripts
between the two tools. The main differences are the file location (.faheem-code/hooks.json vs .claude/settings.json)
and tool names (e.g., terminal vs Bash).
Hook definition fields
| Field | Type | Default | Description |
|---|---|---|---|
type | string | "command" | Hook type (currently only "command" is supported) |
command | string | (required) | Shell command or path to script to execute |
timeout | integer | 60 | Maximum execution time in seconds |
async | boolean | false | Run in background without waiting for result |
Matcher patterns
The matcher field determines which tools trigger the hook (only relevant for PreToolUse and PostToolUse):
| Pattern | Description | Example |
|---|---|---|
* | Matches all tools | "matcher": "*" |
| Exact name | Matches a specific tool | "matcher": "terminal" |
| Regex | Auto-detected or wrapped in / | "matcher": "/terminal|browser/" |
For hooks that aren't tool-specific (Stop, UserPromptSubmit, SessionStart, SessionEnd), set the matcher to "*" or omit it.
How hook scripts work
Input
Hook scripts receive a JSON payload on stdin with details about the event:
{
"event_type": "PreToolUse",
"tool_name": "terminal",
"tool_input": { "command": "rm -rf /tmp/data" },
"session_id": "abc-123",
"working_dir": "/workspace"
}
The following environment variables are also set:
| Variable | Description |
|---|---|
FAHEEMCODE_EVENT_TYPE | The hook event type (e.g., PreToolUse) |
FAHEEMCODE_TOOL_NAME | The tool being used (for tool hooks) |
FAHEEMCODE_PROJECT_DIR | The project working directory |
FAHEEMCODE_SESSION_ID | The current session ID |
Output
Hook scripts communicate results through exit codes and optional JSON on stdout:
Exit codes:
0- Success. The operation proceeds normally.2- Block. The operation is denied.- Any other code - Error. The operation proceeds, but the error is logged.
JSON output (optional):
{
"decision": "deny",
"reason": "rm -rf commands are blocked for safety",
"additionalContext": "Extra context to pass to the agent"
}
| Field | Description |
|---|---|
decision | "allow" or "deny" - overrides the exit code |
reason | Human-readable explanation shown in the UI |
additionalContext | Additional context injected into the agent's prompt |
Real-world example
The Faheem Code Agent SDK repository uses a Stop hook that runs pre-commit checks, targeted pytest suites, and GitHub CI status verification before allowing the agent to finish:
.faheem-code/hooks.json- hook configuration.faheem-code/hooks/on_stop.sh- the stop hook script
This hook setup prevents the agent from finishing if pre-commit, tests, or CI are failing.
More examples
Block dangerous commands (PreToolUse)
Prevent the agent from running destructive shell commands:
#!/bin/bash
# Read JSON input from stdin
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command // ""')
if [[ "$command" =~ "rm -rf" ]]; then
echo '{"decision": "deny", "reason": "rm -rf commands are blocked for safety"}'
exit 2
fi
exit 0
{
"pre_tool_use": [
{
"matcher": "terminal",
"hooks": [{ "command": ".faheem-code/hooks/block_dangerous.sh", "timeout": 10 }]
}
]
}
Enforce linting before stop
Don't let the agent finish until linting passes - this is what prevents linting errors from being committed:
#!/bin/bash
cd "$FAHEEMCODE_PROJECT_DIR"
# Run pre-commit or your linter
if ! pre-commit run --all-files 2>&1; then
echo '{"decision": "deny", "reason": "Linting failed. Fix the issues before finishing."}'
exit 2
fi
exit 0
{
"stop": [
{
"matcher": "*",
"hooks": [{ "command": ".faheem-code/hooks/lint_on_stop.sh", "timeout": 120 }]
}
]
}
Log all tool usage (PostToolUse)
Log every tool the agent uses for auditing:
#!/bin/bash
echo "[$(date)] Tool: $FAHEEMCODE_TOOL_NAME" >> /tmp/tool_usage.log
exit 0
{
"post_tool_use": [
{
"matcher": "*",
"hooks": [{ "command": ".faheem-code/hooks/log_tools.sh", "timeout": 5 }]
}
]
}
Inject Git context (UserPromptSubmit)
Automatically include git status when the user asks about code changes:
#!/bin/bash
input=$(cat)
if echo "$input" | grep -qiE "(changes|diff|git|commit|modified)"; then
if git rev-parse --git-dir > /dev/null 2>&1; then
status=$(git status --short 2>/dev/null | head -10)
if [ -n "$status" ]; then
escaped=$(echo "$status" | sed 's/"/\\"/g' | tr '\n' ' ')
echo "{\"additionalContext\": \"Current git status: $escaped\"}"
fi
fi
fi
exit 0
Combine multiple hooks
You can configure multiple hook types and multiple hooks per event:
{
"pre_tool_use": [
{
"matcher": "terminal",
"hooks": [
{ "command": ".faheem-code/hooks/block_dangerous.sh", "timeout": 10 }
]
}
],
"post_tool_use": [
{
"matcher": "*",
"hooks": [
{ "command": ".faheem-code/hooks/log_tools.sh", "timeout": 5, "async": true }
]
}
],
"stop": [
{
"matcher": "*",
"hooks": [
{ "command": ".faheem-code/hooks/lint_on_stop.sh", "timeout": 120 }
]
}
]
}
Viewing active hooks
Use the /skills command in the CLI to see loaded skills, hooks, and MCPs for the current session.
Active hooks are loaded automatically when a conversation starts. Hook execution events appear in the conversation log, showing whether each hook allowed or blocked an operation.
Tips
- Keep hooks fast. Hooks run synchronously (unless marked
async) and add latency to agent actions. Use reasonable timeouts. - Use
jqfor JSON parsing. Hook scripts receive JSON input on stdin. Thejqtool is available in the sandbox for parsing fields liketool_input.command. - Use environment variables for simple hooks. For PostToolUse hooks that only need the tool name,
$FAHEEMCODE_TOOL_NAMEavoids the need for JSON parsing. - Test hooks locally. You can test hook scripts by piping JSON to them:
echo '{"event_type":"Stop"}' | bash .faheem-code/hooks/lint_on_stop.shecho "Exit code: $?"
- Async hooks can't block. Hooks with
"async": truerun in the background and cannot block operations. Use async for logging or telemetry, not for enforcement.
See also
- Repository Customization - Setup scripts and repository-specific hooks
- Skills - Extend agent behavior with prompt-based skills
- Hooks (SDK Guide) - Programmatic hooks for SDK developers