Customize
Hooks
Run your own commands at fixed points in Bwat's lifecycle: veto tools before they run, notify your team, enforce house rules.
On this page
What hooks are
Hooks are your own shell commands, run automatically at fixed points in Bwat's lifecycle. They're the automation layer for the things Bwat shouldn't have to remember: lint after every edit, veto the one command you never want run, post to your team channel when a task finishes, block prompts that match a policy.
You configure them in two plain JSON files, with no code inside Bwat and no plugin system to learn:
| Scope | File | Applies to |
|---|---|---|
| User | ~/.bwat/hooks.json | Every project on this machine |
| Project | .bwat/hooks.json | This workspace only |
Both locations are inside auto-gitignored directories, so a repository can never carry executable hooks to a teammate. Hooks are always something the machine's owner opted into. Edits are picked up immediately; BWAT_SKIP_HOOKS=1 disables all hooks if you ever need a kill switch.
A first hook
This project-scope hook vetoes any git push --force Bwat tries to run in every permission mode, including Auto, where there would otherwise be no prompt to stop it:
{
"version": 1,
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "grep -q 'push --force' <<< \"$TOOL_INPUT\" && { echo 'no force pushes' >&2; exit 2; } || exit 0"
}
]
}
]
}
}If Bwat later tries git push --force, the hook exits with code 2 and Bwat receives the error no force pushes instead of running the command. It sees the reason and adapts, exactly as if you had denied the action yourself.
The events
| Event | Fires | What a block (exit 2) does |
|---|---|---|
PreToolUse | Immediately before any tool runs: Bwat's own tools, sub-agents, everything | Vetoes the call; the tool body never runs and Bwat gets your stderr as the reason |
PostToolUse | After a tool result is produced | Appends your feedback to the result Bwat sees |
UserPromptSubmit | At the start of a run, before the first model call | Blocks the whole prompt; you see the reason in chat |
Stop | When a run finishes naturally (not on cancels) | Nothing yet: notification only |
SessionStart | Once per session, at the first run | Nothing |
SessionEnd | CLI exit, or closing the VS Code panel | Nothing |
Placement, precisely: PreToolUse runs after your approval in Manual or Edit-automatically modes, so a hook can veto but never silently approve something you haven't confirmed. In Auto mode (where there are no prompts), it is the pre-execution guardrail.
The contract
Each hook command runs through your shell with the working directory set to the workspace, and receives one JSON object on stdin describing what's happening:
{
"event": "PreToolUse",
"sessionId": "agent_…",
"cwd": "/path/to/workspace",
"tool_name": "BashTool",
"tool_input": { "command": "git push --force origin main" }
}PostToolUse adds tool_result; UserPromptSubmit includes the prompt text. Exit codes are the whole decision:
| Exit code | Meaning |
|---|---|
0 | Continue: nothing happens |
2 | Block: stderr becomes the feedback Bwat (or you) sees |
| anything else / crash / timeout | Non-blocking: logged as a note, the run carries on |
Hooks are deliberately fail-open: a broken hook can log noise, but it can never take a run down. Timeouts default to 60 seconds (set timeoutMs per hook, capped at 10 minutes) and count as non-blocking. A timed-out hook's whole process tree is killed.
Matchers
The optional matcher is a regular expression tested against both the tool's wire name (BashTool) and short form (Bash), so "matcher": "Bash" covers the shell tool. Omit it and the hook fires for every tool. Only the two tool events use matchers; the lifecycle events always fire.
Ideas
- Veto list: block
rm -rfin a monorepo,kubectl deleteagainst production, or edits toinfrastructure/via aPreToolUsematcher onWrite|Edit. - Lint on edit: a
PostToolUsehook onEdit|Writethat runs your formatter; a non-zero exit surfaces as a note. - Team notifications: a
Stophook that posts “Bwat finished in <project>” to Slack. - Prompt policy: a
UserPromptSubmithook that rejects prompts containing secrets you don't want sent anywhere.
Hooks pair naturally with your own MCP servers: allow the tools you've opted into, veto everything else.

