Skip to content

Hooks

Hooks are shell commands that fire before or after any tool call, filtered by tool-name glob patterns. They’re the main way to customize AI Butler’s behavior without writing Go code.

  • Auto-format on save — run gofumpt or prettier after every file.edit
  • Block dangerous commands — refuse shell.exec when the command matches a dangerous pattern
  • Audit tool use — append every shell.exec to a log file
  • Notify on completion — ping a webhook when a long task finishes
  • Enforce policy — run a linter and block commits that fail it

Hooks live in config.yaml under configurations.hooks. There are two events: pre_tool_use (before the tool runs) and post_tool_use (after it completes).

Each hook has a shell command and a list of tool-name tools glob patterns. If the tool being called matches any pattern, the hook fires.

configurations:
hooks:
pre_tool_use:
- command: "echo 'blocked' >&2 && exit 2"
tools: ["shell.exec"] # fires on every shell call
post_tool_use:
- command: "gofumpt -w ."
tools: ["file.edit", "file.write"]
- command: "echo '$(date) $TOOL_NAME' >> /var/log/aibutler-audit.log"
tools: ["*"] # fires on every tool call

Glob-style, matched against the tool name:

PatternMatches
file.editExact match only
file.*All file tools (file.read, file.write, file.edit, file.list, file.search)
git.*All git tools
iot.safety.*Safety-tier IoT tools only
*Every tool call

Hook commands run with these environment variables set:

VariableValue
TOOL_NAMEName of the tool being called (e.g. file.edit)
TOOL_INPUTJSON-encoded tool input
TOOL_OUTPUTTool output (post-hooks only)
TOOL_STATUSsuccess or error (post-hooks only)
SESSION_IDCurrent session ID
CHANNELChannel name (telegram, terminal, etc.)

Pre-hooks can block tool execution by their exit code:

Exit codeMeaning
0Success — tool call proceeds
1Non-fatal failure — warning logged, tool call still proceeds
2Block — tool call is refused, stderr is returned to the agent as a feedback message

Post-hooks’ exit codes are logged but don’t affect the already-completed tool call.

configurations:
hooks:
pre_tool_use:
- command: |
if echo "$TOOL_INPUT" | grep -qE 'rm -rf /|dd if=|mkfs\.'; then
echo "Refused: destructive shell command" >&2
exit 2
fi
tools: ["shell.exec"]
configurations:
hooks:
post_tool_use:
- command: |
path=$(echo "$TOOL_INPUT" | jq -r .path)
if [[ "$path" == *.go ]]; then
gofumpt -w "$path"
fi
tools: ["file.edit", "file.write"]
configurations:
hooks:
post_tool_use:
- command: "printf '%s %s %s\n' \"$(date -Iseconds)\" \"$TOOL_NAME\" \"$TOOL_STATUS\" >> /var/log/aibutler-audit.log"
tools: ["*"]

Hooks run as the same OS user as AI Butler itself. They inherit the same shell sandbox (if configured) — see Sandbox configuration. Do not put secrets in hook commands; they’re logged verbatim on failure.