Problem
The driftcheck.sh Stop hook flags every git-tracked *.sh file that has a shebang but no execute permission. Some repos have files like that on purpose — e.g. in navecho_monorepo, the workstation_install settings.sh/ros_config.sh files are only ever sourced by deploy_install.sh, and the run scripts get chmod'd at their install destination, so their execute bits were deliberately removed (repo commit f979acd6).
Because it's a Stop hook that exits 2, there is no way to acknowledge the exception: it blocks the end of every Claude Code session in that repo, and the only escapes are flipping the exec bits back (reverting an intentional commit) or stripping shebangs (breaking the scripts at their install destination).
Fix
Add per-repo opt-out support: an optional .driftcheckignore file at the repo root, one glob pattern per line (matched with bash [[ == ]] against the git-relative path), blank lines and # comments allowed. Files matching a pattern are skipped; everything else is still checked.
diff --git a/agentic-ai/Claude/hooks/driftcheck.sh b/agentic-ai/Claude/hooks/driftcheck.sh
index d0eadd4..4c372ef 100755
--- a/agentic-ai/Claude/hooks/driftcheck.sh
+++ b/agentic-ai/Claude/hooks/driftcheck.sh
@@ -6,15 +6,30 @@
# - has shebang but not executable → flag (meant to run but can't)
# - is executable but no shebang → flag (can run but no interpreter declared)
# Library/sourced files (no shebang, not executable) are intentionally skipped.
+# Repos can opt files out via .driftcheckignore at the repo root: one glob
+# pattern per line (matched against the git-relative path), # for comments.
set -euo pipefail
trap 'exit 2' ERR
git rev-parse --git-dir &>/dev/null || exit 0
+ignore_file="$(git rev-parse --show-toplevel)/.driftcheckignore"
+ignored() {
+ local f=$1 pat
+ [[ -f "$ignore_file" ]] || return 1
+ while IFS= read -r pat; do
+ [[ -z "$pat" || "$pat" == '#'* ]] && continue
+ # shellcheck disable=SC2053
+ [[ "$f" == $pat ]] && return 0
+ done < "$ignore_file"
+ return 1
+}
+
issues=()
while IFS= read -r f; do
[[ -f "$f" ]] || continue
+ ignored "$f" && continue
read -r first_line < "$f" || first_line=""
has_shebang=false; is_exec=false
[[ "$first_line" == '#!'* ]] && has_shebang=true
Example .driftcheckignore:
# Intentionally non-executable despite shebangs:
# settings.sh/config.sh are sourced; run scripts are chmod'd at install.
docker/local_account.sh
workstation_install/global_run.sh
workstation_install/workstations/*/settings.sh
workstation_install/workstations/*/config.sh
Status
The fix is already applied to the working copy at agentic-ai/Claude/hooks/driftcheck.sh (uncommitted) and verified against test_repo: with the ignore file present the hook exits 0; other violations are still reported. Needs commit.
🤖 Generated with Claude Code
Problem
The
driftcheck.shStop hook flags every git-tracked*.shfile that has a shebang but no execute permission. Some repos have files like that on purpose — e.g. innavecho_monorepo, theworkstation_installsettings.sh/ros_config.shfiles are only eversourced bydeploy_install.sh, and the run scripts getchmod'd at their install destination, so their execute bits were deliberately removed (repo commitf979acd6).Because it's a Stop hook that exits 2, there is no way to acknowledge the exception: it blocks the end of every Claude Code session in that repo, and the only escapes are flipping the exec bits back (reverting an intentional commit) or stripping shebangs (breaking the scripts at their install destination).
Fix
Add per-repo opt-out support: an optional
.driftcheckignorefile at the repo root, one glob pattern per line (matched with bash[[ == ]]against the git-relative path), blank lines and#comments allowed. Files matching a pattern are skipped; everything else is still checked.Example
.driftcheckignore:Status
The fix is already applied to the working copy at
agentic-ai/Claude/hooks/driftcheck.sh(uncommitted) and verified againsttest_repo: with the ignore file present the hook exits 0; other violations are still reported. Needs commit.🤖 Generated with Claude Code