feat: add pre-spawn freshness hook to auto-fetch main before agent spawn #197

Merged
forgejo_admin merged 1 commit from 193-pre-spawn-freshness-hook into main 2026-03-28 05:33:36 +00:00
Contributor

Summary

Adds a PreToolUse hook that automatically fetches and fast-forwards local main before every agent spawn, preventing worktrees from branching off stale code. Best-effort only -- never blocks.

Changes

  • hooks/pre-spawn-freshness.sh -- new hook that detects the repo remote (forgejo preferred, origin fallback), fetches main, and fast-forwards via git update-ref if local is behind
  • settings.json -- registers the hook on the Task matcher, before check-agent-spawn.sh

Test Plan

  • Verified spawn JSON with valid git CWD: exits 0, fetches and fast-forwards when stale, silent when fresh
  • Verified non-spawn JSON (no prompt field): exits 0 silently
  • Verified non-git CWD (/tmp): exits 0 silently
  • Verified stale main scenario: correctly logs [pre-spawn-freshness] Updated local main to <sha>
  • Verified diverged history: refuses force-update, exits 0 silently
  • Run command: echo '{"tool_input":{"prompt":"test","subagent_type":"dev"},"cwd":"/home/ldraney/claude-custom"}' | bash hooks/pre-spawn-freshness.sh

Review Checklist

  • Hook follows existing patterns (INPUT=$(cat), jq parsing, set -euo pipefail)
  • Never blocks -- all failure paths exit 0
  • Does not modify check-agent-spawn.sh
  • Runs before check-agent-spawn.sh in Task matcher
  • Detects forgejo remote, falls back to origin
  • Uses git update-ref for fast-forward (no checkout needed)
  • Incident 2026-03-06: stale main caused agent PR that would have destroyed production resources

Closes #193

## Summary Adds a PreToolUse hook that automatically fetches and fast-forwards local main before every agent spawn, preventing worktrees from branching off stale code. Best-effort only -- never blocks. ## Changes - `hooks/pre-spawn-freshness.sh` -- new hook that detects the repo remote (forgejo preferred, origin fallback), fetches main, and fast-forwards via `git update-ref` if local is behind - `settings.json` -- registers the hook on the Task matcher, before `check-agent-spawn.sh` ## Test Plan - Verified spawn JSON with valid git CWD: exits 0, fetches and fast-forwards when stale, silent when fresh - Verified non-spawn JSON (no `prompt` field): exits 0 silently - Verified non-git CWD (`/tmp`): exits 0 silently - Verified stale main scenario: correctly logs `[pre-spawn-freshness] Updated local main to <sha>` - Verified diverged history: refuses force-update, exits 0 silently - Run command: `echo '{"tool_input":{"prompt":"test","subagent_type":"dev"},"cwd":"/home/ldraney/claude-custom"}' | bash hooks/pre-spawn-freshness.sh` ## Review Checklist - [x] Hook follows existing patterns (INPUT=$(cat), jq parsing, set -euo pipefail) - [x] Never blocks -- all failure paths exit 0 - [x] Does not modify check-agent-spawn.sh - [x] Runs before check-agent-spawn.sh in Task matcher - [x] Detects forgejo remote, falls back to origin - [x] Uses git update-ref for fast-forward (no checkout needed) ## Related Notes - Incident 2026-03-06: stale main caused agent PR that would have destroyed production resources ## Related Closes #193
Prevents worktrees from branching off stale local main by fetching and
fast-forwarding refs/heads/main via update-ref before every agent spawn.
Best-effort only -- network failures exit 0 silently, never blocks.

Closes #193

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Contributor

PR #197 Review

DOMAIN REVIEW

Tech stack: Bash hooks for Claude Code PreToolUse system, JSON configuration (settings.json).

Hook quality (pre-spawn-freshness.sh):

  • Follows established hook patterns: INPUT=$(cat), jq parsing, set -euo pipefail, fail-open exit 0 on every non-applicable or error path.
  • Fail-safe design is correct and critical for a PreToolUse hook -- network failures, non-git dirs, missing refs, and diverged history all bail silently without blocking the spawn.
  • Remote detection logic (prefer forgejo remote name, fall back to origin) is not a DRY violation with forgejo-helper.sh. The helper detects Forgejo by URL hostname for API access; this hook detects by remote name for fetch targeting. Different concerns.
  • The merge-base --is-ancestor guard (line 58) prevents force-updates on diverged history. The update-ref call with the old-value CAS parameter (line 62) prevents TOCTOU races. This is notably more robust than the existing post-merge-rebase.sh and post-mcp-merge-rebase.sh, which both lack the ancestor check and CAS old-value.
  • Comment header documents the matcher, purpose, fail-open policy, and the originating incident. Clean.

settings.json change:

  • Hook registered as the first entry in the Task matcher, before check-agent-spawn.sh. Ordering is correct: freshness must happen before spawn validation so the agent branches from up-to-date main.
  • JSON structure is consistent with all other hook entries.

BLOCKERS

None.

NITS

  1. Existing hooks would benefit from the same robustness: post-merge-rebase.sh (line 61) and post-mcp-merge-rebase.sh (line 57) both call update-ref without the ancestor check or old-value CAS that this new hook correctly includes. Not in scope for this PR, but worth a follow-up ticket to backport the safety pattern. (File paths: /home/ldraney/claude-custom/hooks/post-merge-rebase.sh, /home/ldraney/claude-custom/hooks/post-mcp-merge-rebase.sh.)

  2. Existing post-merge hooks hardcode origin: Both post-merge-rebase.sh and post-mcp-merge-rebase.sh hardcode origin as the remote. This new hook's forgejo-preferred remote detection pattern would improve those hooks too. Again, out of scope -- just noting the improvement opportunity.

  3. Minor: stderr log uses Unicode em dash: Line 6 comment uses a Unicode em dash (). Harmless but inconsistent with hooks like check-agent-spawn.sh which use ASCII dashes. Not worth changing.

SOP COMPLIANCE

  • Branch named after issue: 193-pre-spawn-freshness-hook follows {issue-num}-{kebab-slug}
  • PR body follows template: Summary, Changes, Test Plan, Review Checklist, Related Notes, Related sections all present
  • Related references parent issue: Closes #193
  • No secrets committed
  • No scope creep: exactly 2 files changed, both directly related to the feature
  • Commit messages descriptive

PROCESS OBSERVATIONS

  • Change failure risk: Low. The hook is purely additive and fail-open by design. A broken hook exits 0 and agent spawns proceed unimpeded.
  • Deployment frequency impact: Positive. Eliminates the class of incident where agents branch from stale main, which previously required manual intervention and PR rework.
  • Lead time: Clean single-purpose PR with thorough test plan. Good example of scope discipline.
  • Documentation: The incident reference (2026-03-06) in the hook header provides excellent traceability for future readers.

VERDICT: APPROVED

## PR #197 Review ### DOMAIN REVIEW **Tech stack**: Bash hooks for Claude Code PreToolUse system, JSON configuration (settings.json). **Hook quality (pre-spawn-freshness.sh)**: - Follows established hook patterns: `INPUT=$(cat)`, `jq` parsing, `set -euo pipefail`, fail-open exit 0 on every non-applicable or error path. - Fail-safe design is correct and critical for a PreToolUse hook -- network failures, non-git dirs, missing refs, and diverged history all bail silently without blocking the spawn. - Remote detection logic (prefer `forgejo` remote name, fall back to `origin`) is not a DRY violation with `forgejo-helper.sh`. The helper detects Forgejo by URL hostname for API access; this hook detects by remote name for fetch targeting. Different concerns. - The `merge-base --is-ancestor` guard (line 58) prevents force-updates on diverged history. The `update-ref` call with the old-value CAS parameter (line 62) prevents TOCTOU races. This is notably more robust than the existing `post-merge-rebase.sh` and `post-mcp-merge-rebase.sh`, which both lack the ancestor check and CAS old-value. - Comment header documents the matcher, purpose, fail-open policy, and the originating incident. Clean. **settings.json change**: - Hook registered as the first entry in the `Task` matcher, before `check-agent-spawn.sh`. Ordering is correct: freshness must happen before spawn validation so the agent branches from up-to-date main. - JSON structure is consistent with all other hook entries. ### BLOCKERS None. ### NITS 1. **Existing hooks would benefit from the same robustness**: `post-merge-rebase.sh` (line 61) and `post-mcp-merge-rebase.sh` (line 57) both call `update-ref` without the ancestor check or old-value CAS that this new hook correctly includes. Not in scope for this PR, but worth a follow-up ticket to backport the safety pattern. (File paths: `/home/ldraney/claude-custom/hooks/post-merge-rebase.sh`, `/home/ldraney/claude-custom/hooks/post-mcp-merge-rebase.sh`.) 2. **Existing post-merge hooks hardcode `origin`**: Both `post-merge-rebase.sh` and `post-mcp-merge-rebase.sh` hardcode `origin` as the remote. This new hook's forgejo-preferred remote detection pattern would improve those hooks too. Again, out of scope -- just noting the improvement opportunity. 3. **Minor: stderr log uses Unicode em dash**: Line 6 comment uses a Unicode em dash (`—`). Harmless but inconsistent with hooks like `check-agent-spawn.sh` which use ASCII dashes. Not worth changing. ### SOP COMPLIANCE - [x] Branch named after issue: `193-pre-spawn-freshness-hook` follows `{issue-num}-{kebab-slug}` - [x] PR body follows template: Summary, Changes, Test Plan, Review Checklist, Related Notes, Related sections all present - [x] Related references parent issue: `Closes #193` - [x] No secrets committed - [x] No scope creep: exactly 2 files changed, both directly related to the feature - [x] Commit messages descriptive ### PROCESS OBSERVATIONS - **Change failure risk**: Low. The hook is purely additive and fail-open by design. A broken hook exits 0 and agent spawns proceed unimpeded. - **Deployment frequency impact**: Positive. Eliminates the class of incident where agents branch from stale main, which previously required manual intervention and PR rework. - **Lead time**: Clean single-purpose PR with thorough test plan. Good example of scope discipline. - **Documentation**: The incident reference (2026-03-06) in the hook header provides excellent traceability for future readers. ### VERDICT: APPROVED
forgejo_admin force-pushed 193-pre-spawn-freshness-hook from 5d7f650784 to b116da1dd8 2026-03-28 05:32:18 +00:00 Compare
forgejo_admin deleted branch 193-pre-spawn-freshness-hook 2026-03-28 05:33:36 +00:00
Sign in to join this conversation.
No description provided.