Skip to content

fix(laws): read the hook executable bit from the git index - #17

Merged
ruleofcodedev merged 1 commit into
developfrom
bugfix/hook-exec-bit-from-git-index
Aug 8, 2026
Merged

fix(laws): read the hook executable bit from the git index#17
ruleofcodedev merged 1 commit into
developfrom
bugfix/hook-exec-bit-from-git-index

Conversation

@ruleofcodedev

Copy link
Copy Markdown
Contributor

Closes #15. Reported independently by a Windows consumer who stayed on 7.17.4 because this blocks their build.

The false positive

Git Hook Compliance called every hook of every Windows project non-executable — and the consumer could not act on it. Measured, not assumed:

fs.statSync('.husky/_/pre-commit').mode & 0o777   // 666
fs.chmodSync('.husky/_/pre-commit', 0o755)
fs.statSync('.husky/_/pre-commit').mode & 0o777   // 666  ← unchanged

NTFS has no POSIX execute bit, so chmod +x — the suggestion the law printed — changes nothing. It also judged files git never runs: husky keeps .gitignore, husky.sh and h in its hooks directory.

The true positive, kept

The law was right about the defect underneath: hooks committed at 100644 are not executable when the repo is cloned on Linux. Keeping that was the harder half.

The committed layout is .husky/<hook>, not the resolved hooks directory (.git/hooks, or husky's untracked _). Reading only the resolved directory would have removed the false positive and the true one — my first version scored 100/100 against hooks that were genuinely 100644. The check now reads both.

Our own hooks were committed at 100644. Fixed in this PR — the law was right about us.

Evidence

Proven red and green on this repository:

100644                        → • Hooks committed without the executable bit: commit-msg, pre-commit, pre-push
git update-index --chmod=+x   → Git Hook Compliance (Score: 100/100)

Plus five regression tests on a real git fixture with committed hooks and an untracked husky _ directory.

Verdict impact

Changes verdicts: the unactionable Windows violation disappears; committed-but-non-executable hooks are still reported, with an actionable message (git update-index --chmod=+x). Three new detectionLimits entries declare what is now not checked — untracked hooks carry no mode to judge.

Gate

  • npm run lint — 0 errors
  • npx jest — 505 suites / 13871 tests, exit 0
  • npm run canary — green (py100 / ng136 / node78)
  • node dist/cli.js audit — exit 0

Git Hook Compliance called every hook of every Windows project
non-executable, and the consumer could not act on it. NTFS carries no
POSIX execute bit: `fs.stat().mode` is 0o666 for every file there and
`chmod` is a no-op, so `chmod +x` — the suggestion the law printed —
changes nothing. It also judged files git never runs: husky keeps
`.gitignore`, `husky.sh` and `h` in its hooks directory.

The bit now comes from the git index (`100755` / `100644`), which
answers the same question identically on every platform and only for
files a consumer can commit a mode for. Untracked hooks are not judged
at all: husky ignores its generated `_` directory wholesale, so no mode
exists there for git or anyone else to set.

The law was right about the defect underneath, and keeping that was the
harder half. The committed layout is `.husky/<hook>`, not the resolved
hooks directory, so the check reads both — otherwise removing the false
positive would have removed the true one with it. Our own hooks were
committed at 100644 and are fixed here; a Linux clone would not have
executed them.

Reported by a Windows consumer whose build it blocked, having stayed on
the previous version. Proven red and green, on this repository and on a
real git fixture.
@ruleofcodedev
ruleofcodedev merged commit 3cec9d3 into develop Aug 8, 2026
2 checks passed
@ruleofcodedev
ruleofcodedev deleted the bugfix/hook-exec-bit-from-git-index branch August 8, 2026 12:59
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