Skip to content

fix: make the pre-push hook actually guard the default branch - #2

Merged
oddurs merged 1 commit into
mainfrom
fix/pre-push-branch-guard
Sep 11, 2026
Merged

oddurs merged 1 commit into
mainfrom
fix/pre-push-branch-guard

Conversation

@oddurs

@oddurs oddurs commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Problem

The pre-push hook was supposed to refuse a direct push to the default branch.
It never did. Exercised with the exact stdin git supplies, it exited 0:

$ echo "refs/heads/main $SHA refs/heads/main $SHA" | .githooks/pre-push origin <url>
...
check: PASS
hook exit=0

The cause is a pipeline-status bug:

default=$(git symbolic-ref --quiet --short "refs/remotes/$remote/HEAD" \
    | sed "s|^$remote/||" || echo main)

sed exits 0 even when git failed, so the || echo main fallback never fires
and default is silently empty. Every subsequent guard then compares against
refs/heads/ and matches nothing.

The same mistake was in scripts/agent's default_branch, where it showed up as
an empty line in scripts/agent doctor — visible, and fixed before it was
committed. Here it was invisible, because a guard that never fires looks exactly
like a guard with nothing to catch.

Approach

Assign, then test. Also dropped a dead all-zero-sha line that did nothing;
deletions are refused by branch protection.

Look at this sceptically

  • Verified both directions, not just the refusal — a guard that rejects
    everything would also have "passed" the first test:

    input result
    refs/heads/main refuses, exit 1
    refs/heads/fix/x runs scripts/task check, passes
  • Hooks are advisory. Anyone can --no-verify, and an agent that never ran
    scripts/setup has no hooks at all. Branch protection on the remote is the
    real enforcement; this only makes the failure fast and local.

Exercised with the stdin git supplies, the hook exited 0 on a push to main.

  default=$(git symbolic-ref ... | sed "s|^$remote/||" || echo main)

sed exits 0 even when git failed, so the fallback never fired and default was
silently empty — every guard below then compared against "refs/heads/" and
matched nothing. Assign first, then test.

The same mistake was in scripts/agent's default_branch, where it surfaced as an
empty line in doctor. Here it was invisible: a guard that never fires looks
exactly like a guard with nothing to catch.

Verified both directions — refuses refs/heads/main, and still runs the full
check for a feature branch.
@oddurs
oddurs merged commit 5b97b92 into main Sep 11, 2026
2 checks passed
@oddurs
oddurs deleted the fix/pre-push-branch-guard branch September 11, 2026 06:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant