feat: detect stale worktree branches when main advances mid-session #206
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/claude-custom!206
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "136-detect-stale-worktree-branches"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Adds a PreToolUse hook that warns dev agents when their worktree branch is stale against
origin/mainbefore PR creation. This closes the gap where external merges (sibling agents, CI, manual pushes) advance main during an active session, and the agent only discovers divergence at PR time.Changes
hooks/check-branch-freshness.sh-- New hook that fetchesorigin/main(orforgejo/main), computes the merge-base of the current branch, and emits a structured warning with commit count when the branch has diverged. Notification-only: usespermissionDecision: "allow"so PR creation proceeds but the agent sees the warning.settings.json-- Wires the hook as PreToolUse onmcp__forgejo__submit_pr, positioned beforecheck-pr-template.shso the agent sees the freshness warning first.Design Decisions
mcp__forgejo__submit_pronly -- The acceptance criteria requires the check not fire on every tool call. Forgejo is the source of truth for PRs, so this is the primary gate. Not added to the Bash matcher (would fire on every bash command even with early-exit filtering).refs/remotes/origin/main, thenrefs/remotes/origin/master) before falling back tosymbolic-ref refs/remotes/origin/HEAD. This avoids false positives whenorigin/HEADpoints to a feature branch (common in local clones).permissionDecision: "allow"with apermissionDecisionReasoncontaining the warning. Does not block, does not auto-rebase. Matches the issue's resolved design decision.git fetchfailure exits silently (exit 0). Network issues never block PR creation.Test Plan
bash -n)origin/mainby 1 commit, hook correctly emittedSTALE BRANCH WARNING: origin/main has advanced 1 commit(s)withpermissionDecision: "allow"post-merge-rebase.shandpost-mcp-merge-rebase.share untouchedsettings.jsonis still valid JSON after editReview Checklist
set -euo pipefail, reads$INPUTfrom stdin viajq, emits structured JSONorigin) and Forgejo (forgejo) remotesgit -C "$CWD"throughout)git fetch $REMOTE $DEFAULT_BRANCH --quietonly)Related Notes
worktree-workflowSOP in pal-e-docsconvention-worktree-isolationin pal-e-docsRelated
PR #206 Review
DOMAIN REVIEW
Tech stack: Bash shell hook (Claude Code PreToolUse hook framework), JSON settings wiring.
Shell scripting quality:
The hook follows established patterns well. Specific observations:
set -euo pipefailis correct -- matches the majority of hooks in this codebase (check-agent-spawn.sh,block-penny-writes.sh,cleanup-worktrees.sh, etc.). The siblingcheck-pr-template.shusesset -eo pipefail(withoutu), but that is the exception, not the norm. The stricterset -u(nounset) is appropriate here since all variables are properly initialized before use.Remote detection is a genuine improvement over existing hooks. The post-merge-rebase hooks (
post-merge-rebase.sh,post-mcp-merge-rebase.sh) hardcodeoriginand only checkrefs/heads/main(local refs). This new hook checks for aforgejoremote first and usesrefs/remotes/${REMOTE}/main(remote tracking refs). This is the correct approach for a pre-PR freshness check -- the PR body's Design Decisions section explains this well.Default branch detection order is intentionally different from post-merge hooks -- and the comment on lines 49-51 explains why. Checking concrete remote refs (
show-ref --verify) beforesymbolic-refavoids false positives whenorigin/HEADpoints to a feature branch. This is documented and justified.Best-effort network pattern --
git fetchfailure exits silently (exit 0). This is the right design for a notification-only hook. Network flakiness must never block PR creation.All git commands use
git -C "$CWD"consistently -- correct for worktree contexts where the hook process may not be in the repo directory.JSON output uses
jq -nwith--arg-- properly escapes the reason string. No injection risk.One DRY observation (non-blocking): The default branch detection logic now exists in three variants across the codebase:
post-merge-rebase.shlines 39-49:symbolic-reffirst, thenrefs/heads/main|masterpost-mcp-merge-rebase.shlines 35-45: identical to abovecheck-branch-freshness.shlines 49-63:refs/remotes/*/main|masterfirst, thensymbolic-reffallbackThese serve different purposes (local vs. remote refs), but the shared logic could eventually be extracted to a helper function in
forgejo-helper.sh. Not a blocker for this PR -- the implementations are intentionally different because they check different ref types.BLOCKERS
None.
Regarding the test coverage blocker criterion: This repo has exactly one test file (
tests/test_block_groupme_send.sh), and the vast majority of hooks (20+) have no unit tests. The PR's Test Plan section documents manual verification of both the stale and fresh scenarios, plus syntax checking withbash -n. While test coverage for this hook would be valuable, applying a hard "zero tests = blocker" standard would be inconsistent with how every other hook in this repo has been reviewed and merged. The existing test precedent in this repo makes this a nit, not a blocker.No unvalidated user input -- the hook only reads
$INPUT.cwdfrom the Claude Code harness (trusted context, not user-supplied).No secrets or credentials in code.
No DRY violation in auth/security paths.
NITS
Minor DRY opportunity: The
DEFAULT_BRANCHdetection logic is now in 3 hooks. Consider extracting a_detect_default_branch()function intoforgejo-helper.shin a future cleanup ticket. The three variants serve slightly different purposes (local refs vs remote refs, different remote names), so this would need a parameter for ref type.Test coverage: A unit test following the
test_block_groupme_send.shpattern would strengthen this. Test cases: (a) fresh branch emits no output, (b) stale branch emits allow+warning, (c) non-git directory exits silently, (d) main branch exits silently. This could be a follow-up ticket.Unicode character in comment: Line 12 uses an em dash (
---). The existing hooks use ASCII double-dash (--) in comments. Very minor style consistency point.SOP COMPLIANCE
136-detect-stale-worktree-branchesmatches issue #136worktree-workflowandconvention-worktree-isolationSOPs, which is appropriate for a standalone board itemPROCESS OBSERVATIONS
permissionDecision: "allow"), so it cannot break any existing workflow. Zero change-failure risk from the hook itself.check-branch-freshness.shfires beforecheck-pr-template.sh, so the agent sees the staleness warning before any template validation denial. This is the right sequence -- no point validating a PR template if the branch is stale.VERDICT: APPROVED