Skip to content

chore: refuse a push to main in every spelling with a pre-call hook - #38

Merged
cristiangirlea merged 2 commits into
mainfrom
chore/push-guard-hook
Oct 15, 2026
Merged

cristiangirlea merged 2 commits into
mainfrom
chore/push-guard-hook

Conversation

@cristiangirlea

Copy link
Copy Markdown
Owner

Why

.claude/settings.json denies the usual spellings of a push to main, but a pattern list cannot cover every one: a bare git push while main is checked out, git push origin HEAD on main, an alias or a shell within a shell reached GitHub's ruleset instead of the hook layer. Closes #25. The ruleset on main (pull request required, merge only, deterministic required) stays the real guard; this is defence in depth, the same hook the owner's other repositories carry.

What changed

  • .claude/hooks/push-guard.sh: verbatim copy of adanext-exec's pre-call hook. It reads every Bash and PowerShell command and refuses a push that would land on main or a tag in the spellings it can read, a force push, git tag, gh release, gh pr merge --auto or --admin, and writes through gh api to releases or refs; a command it cannot read that mentions one of those is refused too. Script files and other programs are not read.
  • .claude/hooks/push-guard.test.sh: its case list, verbatim except one block: the killed-git case executes a shim from the temp dir, and the test containers mount /tmp noexec, so it runs only where a probe script in the temp dir can execute and prints a skip line otherwise.
  • .claude/settings.json: the hook under hooks.PreToolUse for Bash|PowerShell, same shape as the existing post-edit hook.
  • tests/python/test_push_guard_hook.py: both scripts exist and are LF, the settings wiring parsed as JSON, and a run of the case list asserting ok (N cases), so the container suite and CI run the guard's cases.
  • AGENTS.md, CLAUDE.md, .claude/skills/check/SKILL.md, docs/testing.md: the hook, what it does not read, where its cases run, and the Python test count (139; main said 127, stale since chore: add the agent setup (AGENTS.md, CLAUDE.md, .claude) #24 added nine).

Tests run

  • docker compose -f docker/compose.yaml run --rm --no-deps python-tests: 139 tests OK on both commits, about 12 s, the guard's cases included (the killed-git case skipped there).
  • sh .claude/hooks/push-guard.test.sh on the Windows host under Git Bash: ok (223 cases), with the killed-git case included, 6 min 23 s.
  • diff against adanext-exec: the hook is identical; the test script differs only in the probe/skip block.

Review

Independent review of 0634885: nothing blocking. It verified the verbatim copy, the LF attributes, the wiring, the test count, the ruleset on main, and traced this repository's own allowed commands through the hook (compose commands, gh pr merge <n> --merge --delete-branch --match-head-commit, gh workflow run, gh variable delete, gh issue create --body-file): none misfires. Non-blocking: AGENTS.md and CLAUDE.md overstated the hook ("every spelling"); fixed in e1ad78a since this branch wrote those lines. Filed as issues: the hook's cost and 20 s timeout on Windows are unmeasured on a large PR body; the fail-closed case never runs in CI.

settings.json's deny rules match spellings, so a bare git push on main,
git push origin HEAD, an alias or a shell within a shell got through to
GitHub's ruleset. The push guard from the owner's other repositories now
reads every Bash and PowerShell command first and refuses a push that
would land on main or a tag, a force push, git tag, gh release, gh pr
merge --auto or --admin, and a write through gh api to releases or
refs; what it cannot read and mentions one of those, it refuses too.
Its own cases run from the container suite; the killed-git case needs
an executable temporary directory and is skipped where /tmp is noexec.
The review found AGENTS.md and CLAUDE.md promising that the hook
refuses a push in every spelling; it does not open script files or
other programs, by design, so they now say so and name the ruleset on
main as the guard.
@cristiangirlea
cristiangirlea merged commit 263b2d6 into main Oct 15, 2026
6 checks passed
@cristiangirlea
cristiangirlea deleted the chore/push-guard-hook branch October 15, 2026 07:43
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.

Close the push-to-main gaps with a push-guard hook

1 participant