Skip to main content

Gas Town Hooks Management

Centralized Claude Code hook management for Gas Town workspaces.

Overview

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.

Architecture

~/.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:

  1. Start with base config
  2. Apply crew override (if exists)
  3. Apply gastown/crew override (if exists)

Generated targets

Each rig generates settings in shared parent directories (not per-worktree):

TargetPathOverride 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.

Commands

gt hooks sync

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 writing

gt hooks diff

Show what sync would change, without writing anything.

gt hooks diff             # Show differences
gt hooks diff --no-color # Plain output

gt hooks base

Edit 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 list

Show 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 scan

Scan 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 init

Bootstrap 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 install

Browse and install hooks from the registry.

gt hooks registry                  # List available hooks
gt hooks install <hook-id> # Install a hook to base config

Current Registry Hooks

The registry (~/gt/hooks/registry.toml) defines 7 hooks, 5 enabled by default:

HookEventEnabledRoles
pr-workflow-guardPreToolUseYescrew, polecat
session-primeSessionStartYesall
pre-compact-primePreCompactYesall
mail-checkUserPromptSubmitYesall
costs-recordStopYescrew, polecat, witness, refinery
clone-guardPreToolUseNocrew, polecat
dangerous-command-guardPreToolUseNocrew, 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

Design Decision: Registry as Catalog vs Source of Truth

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 install copies 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.

Known Gaps

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. Hook ordering — No action needed currently. The merge chain (base -> override) produces deterministic order, and per-matcher merge ensures one entry per event type.

Integration

gt rig add

When a new rig is created, hooks are automatically synced for all the new rig's targets (crew, witness, refinery, polecats).

gt doctor

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.

Per-matcher merge semantics

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).

Default base config

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