docs/HOOKS.md
Centralized hook management for Gas Town workspaces.
Gas Town manages context injection for all supported agents. The mechanism varies by agent:
| Agent | Hook mechanism | Managed file |
|---|---|---|
| Claude Code, Gemini | settings.json lifecycle hooks | <role>/.claude/settings.json |
| OpenCode | JS plugin | workDir/.opencode/gastown.js |
| GitHub Copilot | JSON lifecycle hooks | workDir/.github/hooks/gastown.json |
| Codex, others | Startup nudge fallback | (no file — nudge only) |
GitHub Copilot note: Copilot CLI supports full executable lifecycle hooks (
sessionStart,userPromptSubmitted,preToolUse,sessionEnd) via.github/hooks/gastown.json. This is the same lifecycle coverage as Claude Code, delivered in Copilot's JSON format rather than Claude'ssettings.jsonformat. Thegt hookscommands below apply to Claude Code (and Gemini) only.
Gas Town manages .claude/settings.json files in gastown-managed parent directories
and passes them to Claude Code via the --settings flag. This keeps customer repos
clean while providing role-specific hook configuration. The hooks system provides
a single source of truth with a base config and per-role/per-rig overrides.
~/.gt/hooks-base.json ← Shared base config (all agents)
~/.gt/hooks-overrides/
├── crew.json ← Override for all crew workers
├── witness.json ← Override for all witnesses
├── gastown__crew.json ← Override for gastown crew specifically
└── ...
Merge strategy: base → role → rig+role (more specific wins)
For a target like gastown/crew:
crew override (if exists)gastown/crew override (if exists)Each rig generates settings in shared parent directories (not per-worktree):
| Target | Path | Override Key |
|---|---|---|
| Crew (shared) | <rig>/crew/.claude/settings.json | <rig>/crew |
| Witness | <rig>/witness/.claude/settings.json | <rig>/witness |
| Refinery | <rig>/refinery/.claude/settings.json | <rig>/refinery |
| Polecats (shared) | <rig>/polecats/.claude/settings.json | <rig>/polecats |
Town-level targets:
mayor/.claude/settings.json (key: mayor)deacon/.claude/settings.json (key: deacon)Settings are passed to Claude Code via --settings <path>, which loads them as
a separate priority tier that merges additively with project settings.
gt hooks syncRegenerate all .claude/settings.json files from base + overrides.
Preserves non-hooks fields (editorMode, enabledPlugins, etc.).
gt hooks sync # Write all settings files
gt hooks sync --dry-run # Preview changes without writing
gt hooks diffShow what sync would change, without writing anything.
gt hooks diff # Show differences
gt hooks diff --no-color # Plain output
gt hooks baseEdit the shared base config in $EDITOR.
gt hooks base # Open in editor
gt hooks base --show # Print current base config
gt hooks override <target>Edit overrides for a specific role or rig+role.
gt hooks override crew # Edit crew override
gt hooks override gastown/witness # Edit gastown witness override
gt hooks override crew --show # Print current override
gt hooks listShow all managed settings.local.json locations and their sync status.
gt hooks list # Show all targets
gt hooks list --json # Machine-readable output
gt hooks scanScan the workspace for existing hooks (reads current settings files).
gt hooks scan # List all hooks
gt hooks scan --verbose # Show hook commands
gt hooks scan --json # JSON output
gt hooks initBootstrap base config from existing settings.local.json files. Analyzes all current settings, extracts common hooks as the base, and creates overrides for per-target differences.
gt hooks init # Bootstrap base and overrides
gt hooks init --dry-run # Preview what would be created
Only works when no base config exists yet. Use gt hooks base to edit
an existing base config.
gt hooks registry / gt hooks installBrowse and install hooks from the registry.
gt hooks registry # List available hooks
gt hooks install <hook-id> # Install a hook to base config
The registry (~/gt/hooks/registry.toml) defines 7 hooks, 5 enabled by default:
| Hook | Event | Enabled | Roles |
|---|---|---|---|
| pr-workflow-guard | PreToolUse | Yes | crew, polecat |
| session-prime | SessionStart | Yes | all |
| pre-compact-prime | PreCompact | Yes | all |
| mail-check | UserPromptSubmit | Yes | all |
| costs-record | Stop | Yes | crew, polecat, witness, refinery |
| clone-guard | PreToolUse | No | crew, polecat |
| dangerous-command-guard | PreToolUse | Yes | crew, polecat |
Additional hooks exist in settings.json files but are not yet in the registry:
bd init* inside .beads/Decision: The registry is a catalog, not the source of truth.
The registry (
registry.toml) lists available hooks. The base/overrides system (~/.gt/hooks-base.json+~/.gt/hooks-overrides/) defines what is active.gt hooks installcopies from the registry into the base/overrides config.This separation provides:
- Per-machine customization (PATH differences across machines)
- Per-role overrides without polluting the shared registry
- Clear distinction between "what hooks exist" and "what hooks are active where"
The registry is the menu. The base/overrides are the order.
Registry doesn't cover all active hooks — Several hooks in settings.json
files are not in registry.toml (bd-init-guard, mol-patrol-guard, tmux-clear,
cwd-validation). These should be added so gt hooks install can manage them.
No gt tap commands beyond pr-workflow — The tap framework has only one
guard implemented. gt tap guard dangerous-command is referenced in the
registry but does not exist yet. Priority order: dangerous-command, bd-init,
mol-patrol, then audit git-push.
No gt tap disable/enable convenience commands — Per-worktree
enable/disable is possible via the override mechanism (gt hooks override
with empty hooks list), but there is no convenience wrapper yet.
Private hooks (settings.local.json) — Claude Code supports
settings.local.json for personal overrides. Gas Town doesn't manage
these yet. Low priority since Gas Town is primarily agent-operated.
Hook ordering — No action needed currently. The merge chain (base -> override) produces deterministic order, and per-matcher merge ensures one entry per event type.
gt rig addWhen a new rig is created, hooks are automatically synced for all the new rig's targets (crew, witness, refinery, polecats).
gt doctorThe hooks-sync check verifies all settings.local.json files match what
gt hooks sync would generate. Use gt doctor --fix to auto-fix
out-of-sync targets.
When an override has the same matcher as a base entry, the override replaces the base entry entirely. Different matchers are appended. An override entry with an empty hooks list removes that matcher.
Example base:
{
"SessionStart": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "gt prime" }] }
]
}
Override for witness:
{
"SessionStart": [
{ "matcher": "", "hooks": [{ "type": "command", "command": "gt prime --witness" }] }
]
}
Result: The witness gets gt prime --witness instead of gt prime
(same matcher = replace).
When no base config exists, the system uses sensible defaults:
gt prime --hookgt prime --hookgt mail check --injectgt costs record