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:
- Start with base config
- Apply
crewoverride (if exists) - Apply
gastown/crewoverride (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.
Regenerate 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 writingShow what sync would change, without writing anything.
gt hooks diff # Show differences
gt hooks diff --no-color # Plain outputEdit the shared base config in $EDITOR.
gt hooks base # Open in editor
gt hooks base --show # Print current base configEdit 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 overrideShow all managed settings.local.json locations and their sync status.
gt hooks list # Show all targets
gt hooks list --json # Machine-readable outputScan 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 outputBootstrap 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 createdOnly works when no base config exists yet. Use gt hooks base to edit
an existing base config.
Browse and install hooks from the registry.
gt hooks registry # List available hooks
gt hooks install <hook-id> # Install a hook to base configThe 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 guard (gastown/crew, beads/crew) - blocks
bd init*inside.beads/ - mol patrol guards (gastown roles) - blocks persistent patrol molecules
- tmux clear-history (gastown root) - clears terminal history on session start
- SessionStart .beads/ validation (gastown/crew, beads/crew) - validates CWD
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 sogt hooks installcan manage them. -
No
gt tapcommands beyond pr-workflow — The tap framework has only one guard implemented.gt tap guard dangerous-commandis 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/enableconvenience commands — Per-worktree enable/disable is possible via the override mechanism (gt hooks overridewith empty hooks list), but there is no convenience wrapper yet. -
Private hooks (settings.local.json) — Claude Code supports
settings.local.jsonfor 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.
When a new rig is created, hooks are automatically synced for all the new rig's targets (crew, witness, refinery, polecats).
The 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:
- SessionStart: PATH setup +
gt prime --hook - PreCompact: PATH setup +
gt prime --hook - UserPromptSubmit: PATH setup +
gt mail check --inject - Stop: PATH setup +
gt costs record