Skip to content

fix(hooks): make driftcheck a nudge and close two silent-failure modes - #70

Merged
ulises-c merged 1 commit into
mainfrom
fix/driftcheck-nudge-and-failure-modes
Jul 30, 2026
Merged

ulises-c merged 1 commit into
mainfrom
fix/driftcheck-nudge-and-failure-modes

Conversation

@ulises-c

Copy link
Copy Markdown
Owner

Closes #69. Closes #67.

Both issues live in agentic-ai/Claude/hooks/driftcheck.sh, so they're fixed together.

#69 — nudge instead of block

The Stop hook exited 2 on any shebang/exec-bit inconsistency, blocking the agent from
finishing until it acted on the findings. For a style check that's heavy-handed: a false
positive (paths that drifted outside a repo's .driftcheckignore globs) pressures the
agent toward the mechanical chmod +x, which for intentionally non-executable sourced
scripts is exactly the wrong action.

Findings now go out as hook JSON with exit 0, plus human-readable lines on stderr for
direct runs:

driftcheck.sh: convention drift (not blocking):
  - has shebang but missing execute permission: path/to/settings.sh
{"systemMessage":"driftcheck: has shebang but missing execute permission: path/to/settings.sh\n"}

trap 'exit 2' ERR is gone, so internal script errors can't block a stop either.

#67 — two failure modes bypassed the exit-2 trap

  • git ls-files failure was invisible. Process substitution hides the exit status
    from pipefail and the ERR trap, so a corrupt index yielded zero lines and a clean
    "no violations" pass — a guard reporting all-clear having checked nothing. The listing
    is captured before the loop and a failure exits 1.
  • Unset HOME exited 1, not 2. set -u treats it as an expansion error and
    terminates before the ERR trap fires. Now an explicit, diagnosed exit 1.

Exit 1 is the new "the check could not run" signal. This hook no longer uses exit 2 at all.

Tests

validate.sh gains four driftcheck.sh regression tests, each verified to fail against
the pre-fix hook (the two silent-failure bugs are precisely the class that hides from a
run-it-and-see check):

[ OK ] driftcheck.sh: flags a shebang script missing its execute bit
[ OK ] driftcheck.sh: honors .driftcheckignore patterns
[ OK ] driftcheck.sh: fails loudly when git ls-files fails
[ OK ] driftcheck.sh: unset HOME fails deliberately

The .driftcheckignore test covers #69's note that the ignore parsing was silently
dropped by a revert once before. The existing repo-state check now distinguishes three
outcomes: fail on non-zero exit, warn on reported drift, pass on clean.

Full validate.sh passes; shellcheck --severity=warning clean on both changed scripts.

Docs

agentic-ai/Claude/README.md hook section, the TODO.md manual-check line, and a
docs/CHANGELOG.md entry.

Note

~/.claude/hooks/driftcheck.sh symlinks into the working tree, so the new behavior is
live on whatever branch is checked out — merging this makes it the default again.

🤖 Generated with Claude Code

Closes #69, closes #67.

The Stop hook blocked with exit 2 on any shebang/exec-bit inconsistency,
forcing the agent to act on findings before it could finish. For a style
check that is heavy-handed: a false positive (paths that drifted outside a
repo's .driftcheckignore globs) pressures the agent toward the mechanical
chmod +x, which can be exactly the wrong action. Drift is now reported as
hook JSON on stdout with exit 0, plus human-readable lines on stderr for
direct runs.

Two failure modes bypassed the old exit-2 trap, both now fixed:

- git ls-files failure was invisible. Process substitution hides the exit
  status from pipefail and the ERR trap, so a corrupt index yielded zero
  lines and a clean pass — all-clear having checked nothing. The listing is
  captured before the loop; failure exits 1.
- Unset HOME tripped set -u at expansion time and terminated before the ERR
  trap with a bare "HOME: unbound variable". Now an explicit exit 1.

Exit 1 is the new "the check could not run" signal; the ERR trap is gone, so
internal errors never block a stop either.

validate.sh gains four driftcheck regression tests, each verified to fail
against the pre-fix hook: flags a shebang script missing its execute bit,
honors .driftcheckignore (parsing a revert dropped silently once before),
fails loudly on a corrupt git index, and fails deliberately with HOME unset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ulises-c
ulises-c merged commit eba92b7 into main Jul 30, 2026
7 checks passed
@ulises-c
ulises-c deleted the fix/driftcheck-nudge-and-failure-modes branch July 30, 2026 22:16
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.

driftcheck.sh: make the Stop hook a nudge instead of a hard block driftcheck.sh: two failure modes bypass the exit-2 trap

1 participant