Summary
doctor's PreToolUse hook runtime check reports fail … exits 127 for a hook that actually works. It does not execute the command recorded in settings.json; it reconstructs a different command from an assumed install layout. On a verified binary-only install that layout does not exist, so the check fails while the hook is healthy.
The suggested fix does not clear it, which makes the state unrecoverable through the documented path.
Environment
- commitlore 0.2.1 (
aarch64-apple-darwin release asset, SHA256SUMS verified OK)
- Installed by extracting the release tarball and copying the single binary to
~/.local/bin/commitlore — the pinned/verified path the README documents, not the install.sh one-liner
- macOS 15 (Darwin 25.3.0), git 2.50.1 (Apple Git-155), no
node on PATH
- Repository with 768 commits;
index --rebuild indexed 175 trailers over 173 commits
Reproduce
# 1. verified binary-only install
gh release download v0.2.1 --repo MongLong0214/commitlore \
--pattern 'commitlore-0.2.1-aarch64-apple-darwin.tar.gz' --pattern SHA256SUMS
grep 'commitlore-0.2.1-aarch64-apple-darwin.tar.gz' SHA256SUMS | shasum -a 256 -c - # OK
tar xzf commitlore-0.2.1-aarch64-apple-darwin.tar.gz
cp commitlore ~/.local/bin/commitlore
# 2. init
commitlore init
init reports 4/4 steps ran, 1 need(s) attention:
[3/4] claude hook install
installed the PreToolUse injection hook: <repo>/.claude/settings.json
matcher: Read|Edit|Write
command: commitlore inject --hook-input # commitlore-inject-hook
[4/4] doctor --fix (final check)
fail PreToolUse hook runtime — the PreToolUse hook exits 127: /bin/bash: /Users/<user>/.local/bin/scripts/commitlore-run.sh: No such file or directory
fix: commitlore inject install-claude-hook
The hook is not broken
Running the command that settings.json actually contains succeeds:
$ echo '{"tool_name":"Read","tool_input":{"file_path":"<repo>/Package.swift"}}' | commitlore inject --hook-input
{"hookSpecificOutput":{"hookEventName":"PreToolUse","additionalContext":"commitlore: active records for Package.swift …"}}
$ echo $?
0
Exit 0, valid hookSpecificOutput, records rendered. commitlore also resolves for a non-login /bin/bash, which is how hooks are invoked.
Root cause
Step 3 writes this command:
commitlore inject --hook-input # commitlore-inject-hook
But the doctor check builds a different one. From the shipped binary:
[installedPath("scripts/commitlore-run.sh"), "inject", "--hook-input"]
~/.local/bin/scripts/commitlore-run.sh does not exist on a binary-only install — that directory is absent entirely — hence 127.
So the check verifies a synthesised command rather than the recorded one. The two disagree whenever the install layout differs from the assumed one.
The suggested fix cannot clear it
$ commitlore inject install-claude-hook
PreToolUse injection hook already installed: <repo>/.claude/settings.json (unchanged)
matcher: Read|Edit|Write
command: commitlore inject --hook-input # commitlore-inject-hook
It correctly detects the hook is already present and changes nothing, so doctor keeps failing. --force is documented for a foreign hook, which this is not. There is no documented path from this state to a clean doctor.
Why it matters
A false fail on a working hook is worse than a missing check. It trains the operator to ignore doctor output, which is exactly the signal that should stay trustworthy — and the project already treats this class as a defect (--help cites #63/#67 for steps being named rather than absorbed into a success message).
Suggested direction
Read the command out of settings.json and execute that, rather than reconstructing a path from an assumed layout. The recorded command is the thing whose runtime behaviour matters, and it is already marked with # commitlore-inject-hook for identification.
If a wrapper script is genuinely required for some install shapes, then claude hook install and the doctor check disagree about which install shape is supported, and that disagreement is the bug.
Summary
doctor's PreToolUse hook runtime check reportsfail … exits 127for a hook that actually works. It does not execute the command recorded insettings.json; it reconstructs a different command from an assumed install layout. On a verified binary-only install that layout does not exist, so the check fails while the hook is healthy.The suggested fix does not clear it, which makes the state unrecoverable through the documented path.
Environment
aarch64-apple-darwinrelease asset,SHA256SUMSverifiedOK)~/.local/bin/commitlore— the pinned/verified path the README documents, not theinstall.shone-linernodeonPATHindex --rebuildindexed 175 trailers over 173 commitsReproduce
initreports4/4 steps ran, 1 need(s) attention:The hook is not broken
Running the command that
settings.jsonactually contains succeeds:Exit 0, valid
hookSpecificOutput, records rendered.commitlorealso resolves for a non-login/bin/bash, which is how hooks are invoked.Root cause
Step 3 writes this command:
But the doctor check builds a different one. From the shipped binary:
~/.local/bin/scripts/commitlore-run.shdoes not exist on a binary-only install — that directory is absent entirely — hence 127.So the check verifies a synthesised command rather than the recorded one. The two disagree whenever the install layout differs from the assumed one.
The suggested fix cannot clear it
It correctly detects the hook is already present and changes nothing, so
doctorkeeps failing.--forceis documented for a foreign hook, which this is not. There is no documented path from this state to a cleandoctor.Why it matters
A false
failon a working hook is worse than a missing check. It trains the operator to ignore doctor output, which is exactly the signal that should stay trustworthy — and the project already treats this class as a defect (--helpcites #63/#67 for steps being named rather than absorbed into a success message).Suggested direction
Read the command out of
settings.jsonand execute that, rather than reconstructing a path from an assumed layout. The recorded command is the thing whose runtime behaviour matters, and it is already marked with# commitlore-inject-hookfor identification.If a wrapper script is genuinely required for some install shapes, then
claude hook installand the doctor check disagree about which install shape is supported, and that disagreement is the bug.