Skip to content

fix(hooks): recognise a relative core.hooksPath so the push guard self-disables - #2249

Merged
BigSimmo merged 8 commits into
mainfrom
claude/push-guard-hookspath-relative
Aug 21, 2026
Merged

fix(hooks): recognise a relative core.hooksPath so the push guard self-disables#2249
BigSimmo merged 8 commits into
mainfrom
claude/push-guard-hookspath-relative

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

  • push-format-guard.sh never recognised its own repository, so it ran a full-repository Prettier check on every push. The hook's header documents that it "runs ONLY when the git hook is absent or not wired to this repo's .githooks directory" and otherwise "exits in milliseconds having done nothing". The wiring test was the suffix glob */.githooks, which requires a / before the name — but core.hooksPath may be absolute or relative to the top of the working tree, and this repo's own npm install writes the bare relative form .githooks. That never matched, so a correctly wired checkout fell straight through to npx --no-install prettier --check .. Measured on the Windows workstation on 2026-08-22, a git push origin HEAD payload ran 103,570 ms and was still going when a 100 s timeout killed it; after this change the same payload exits silently in 1,380 ms. The fix resolves the configured value to a single form and compares it for equality with $repo_root/.githooks.
  • The same change closes a second, quieter hole. The old suffix match accepted any path ending in /.githooks, including a different checkout's — whose pre-push hook does not guard this push at all. The comparison is now exact, so the guard is strictly tighter than before: it fires in a case where it previously stayed silent. The -x "$repo_root/.githooks/pre-push" executability requirement is unchanged, so wired-but-missing and wired-but-not-executable still fall through to the Prettier check.
  • Adds tests/push-format-guard.test.ts; this hook previously had no test coverage at all. Nine cases pin both directions: the three legitimate spellings of a wired core.hooksPath (.githooks, ./.githooks, absolute) stay silent, and unset / missing pre-push / non-executable pre-push / a foreign .githooks all still deny. The fixture rigs npx to report unformatted files so that "the guard ran" is observable as a deny decision rather than as silence — silence is ambiguous, because the hook also exits 0 printing nothing when it self-disables, when npx is missing, and when node_modules/prettier is absent. A test asserting only empty stdout would pass with the guard deleted; these do not.

Verification

  • npm run typecheck — pass ([gate-receipts] recorded a pass for "typecheck:internal" (4371 input files)).
  • npm run lint — pass ([gate-receipts] recorded a pass for "lint:internal" (4371 input files)).
  • npx prettier --check . — repository-wide, not per-file: All matched files use Prettier code style! (exit 0).
  • bash -n .claude/hooks/push-format-guard.sh — syntax OK. Hook stays mode 100755 in the index with CR=0; the new test file is 100644, CR=0.
  • Behavioural mutation test, both directions, before and after the change, driven against a purpose-built fixture repository. Before the fix, hooksPath='.githooks' + executable pre-push produced DENY (the bug) while a foreign .githooks produced silent (the second hole). After the fix all six cases match intent: the three wired spellings are silent, and unset / pre-push absent / foreign .githooks all DENY. The npx-shim mechanism the new test relies on was itself exercised by hand and confirmed to produce a real deny, so the suite is not passing vacuously.

Verification not run: npm run test — the fail-closed PR-local plan selects the full unit suite, but the new suite is describe.skipIf(process.platform === "win32") and therefore cannot execute on this Windows workstation at all; nine tests are collected and skipped. Linux CI is where this coverage actually runs, and it is the authoritative gate for it. Two unrelated suites are also known to fail environmentally on this machine, so a local full run would report reds that are not this diff's.

UI verification not run: no UI, routing, styling, reduced-motion, or forced-colors behaviour is touched.

Risk and rollout

  • Risk: Low, and in the safe direction. The only behavioural change is when the guard declines to run, and the change narrows that set rather than widening it — every input that previously self-disabled the guard either still does (the three wired spellings) or now correctly triggers it (a foreign .githooks). No input that previously triggered the guard now escapes it. scripts/pr-policy.mjs classifies the diff as clinicalRisk: false, operationalRisk: false, ragRanking: false, ui: false.
  • Rollback: revert this single commit. The hook fails open by contract — any parse problem or unexpected state exits 0 with no decision — and .githooks/pre-pushguard-push.mjs remains the real gate throughout, so a revert cannot leave formatting unguarded.
  • Provider or production effects: None.
  • RAG impact: none.

Notes

  • The bug was found while diagnosing unrelated slowness on the Windows workstation, by timing the hook rather than reading it — core.hooksPath returning .githooks is easy to read past.
  • The one case that cannot be reproduced locally is pre-push present but not executable: core.fileMode=false on the ReFS Dev Drive makes chmod -x a silent no-op, so that state is unrepresentable there. It is covered in the new suite for CI.

…f-disables
`push-format-guard.sh` documents that it "runs ONLY when the git hook is absent
or not wired to this repo's .githooks directory" and otherwise "exits in
milliseconds having done nothing". It did not. The wiring test was a suffix glob:
case "$normalised" in
*/.githooks)
`core.hooksPath` is absolute OR relative to the top of the working tree, and this
repo's own `npm install` writes the bare relative form `.githooks`. That value has
no `/` before the name, so it never matched, and a correctly wired checkout fell
through to `npx --no-install prettier --check .` on EVERY push. Measured on the
Windows workstation on 2026-08-22: a `git push origin HEAD` payload ran 103,570 ms
and was still going when a 100 s timeout killed it. After this change the same
payload exits silently in 1,380 ms.
Resolve the value to a single form and compare it for equality with
`$repo_root/.githooks` instead. That also closes a second, quieter hole: the old
suffix match accepted ANY path ending in `/.githooks`, including a different
checkout's, whose pre-push hook does not guard this push at all. The guard is
strictly tighter than before — it now fires in a case where it previously stayed
silent.
The `-x "$repo_root/.githooks/pre-push"` requirement is unchanged, so wired-but-
missing and wired-but-not-executable still fall through to the Prettier check.
Adds tests/push-format-guard.test.ts — this hook previously had no coverage at
all. Nine cases pin both directions: the three legitimate spellings of a wired
`core.hooksPath` stay silent, and unset / missing pre-push / non-executable
pre-push / foreign .githooks all still deny. The fixture rigs `npx` to report
unformatted files so "the guard ran" is observable as a deny decision rather than
as silence, which the hook also produces when it self-disables — a test asserting
only empty stdout would pass with the guard deleted.
Like the sibling hook contract, the suite is skipIf(win32): Windows' `bash.exe`
is a WSL launcher, and `core.fileMode=false` on the ReFS Dev Drive makes the
not-executable case unrepresentable there. It is pinned on Linux CI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@supabase

supabaseBot commented Aug 21, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitaiBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:16 minutes

Limit details: You’ve used the included review currently available. Your 88 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c7d60775-7dc4-40e8-9e8b-07bc5aa0f2a3

📥 Commits

Reviewing files that changed from the base of the PR and between 28bb722 and b68997c.

📒 Files selected for processing (2)
  • .claude/hooks/push-format-guard.sh
  • tests/push-format-guard.test.ts

Comment @coderabbitai help to get the list of available commands.

@BigSimmo
BigSimmo enabled auto-merge (squash) August 21, 2026 19:18

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit:c9d990aac9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread.claude/hooks/push-format-guard.sh Outdated
BigSimmoand others added 3 commits August 22, 2026 03:40
`core.hooksPath` on the Windows workstation can carry different casing from
`CLAUDE_PROJECT_DIR` (`d:/Database/.githooks` vs `D:/Database`) while naming
the same wired directory. Bash `=` is case-sensitive, so the exact-equality
comparison introduced by this branch failed to self-disable there and ran the
full-repository Prettier check on every push - the regression this PR set out
to remove.
Fold case only for the unambiguous `X:/...` drive-letter spelling. The MSYS
`/c/...` form is byte-identical to a real POSIX path, where case IS
significant, so folding it could silently self-disable the guard against a
foreign hooks directory.
Adds Linux-runnable coverage: the fixture creates a literal `D:` directory
inside the scratch root so the drive-letter string the hook compares is exact
while every `$repo_root/...` lookup still resolves to real files. Three
case-variant spellings must stay silent, a case-variant path naming a
different directory must still deny, and POSIX paths must stay case-sensitive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ApB8NBygQn9cosxCQ8omk9
@BigSimmo
BigSimmo disabled auto-merge August 21, 2026 19:42
…tive' into claude/push-guard-hookspath-relative
@BigSimmo
BigSimmo enabled auto-merge (squash) August 21, 2026 19:53
@BigSimmo
BigSimmo merged commit a82625a into mainAug 21, 2026
24 checks passed
@BigSimmo
BigSimmo deleted the claude/push-guard-hookspath-relative branch August 21, 2026 20:11
Sign up for freeto 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.

2 participants

@BigSimmo@claude