fix(hooks): track Bash file modifications to prevent false-positive commit blocks#1483
Conversation
… diff Adds snapshot-pre-bash.sh (PreToolUse Bash) + track-bash-writes.sh (PostToolUse Bash): the pre-hook captures git status --porcelain to a per-worktree temp file before each Bash call; the post-hook diffs the before/after state and appends newly modified or created files to .claude/session-edits.log. This closes the gap where files written by sed -i, printf redirects, tee, heredocs, or build tools (Cargo.lock, lockfiles) were never recorded, causing guard-git.sh to emit false-positive BLOCKED errors. Closes #1457
|
Claude finished @carlos-alm's task —— View job PR Review: fix(hooks): track Bash file modifications to prevent false-positive commit blocks
Root Cause AnalysisProblem Being Solved: Why This Approach: The PR takes a git-state snapshot approach: capture Risk Assessment: Low. Both hooks always exit 0 (informational only). Worst case for a bug: files go un-logged (the current state) or are double-logged (benign since Phase 2: Configuration Change DetectionNo linting rules relaxed. No test timeouts increased. No TypeScript flags changed. No coverage thresholds lowered. The Phase 3: Test Integrity
No test coverage for the new hooks: The hooks are bash scripts exercising Phase 4: Code Quality
|
Greptile SummaryAdds a PreToolUse/PostToolUse Bash hook pair that snapshots
Confidence Score: 4/5Safe to merge for the common case; a residual race exists when two identical Bash commands run concurrently, but this is an edge case and the overall fix is a clear improvement over the pre-PR state. The pre/post hook snapshot approach correctly handles the vast majority of Bash-write scenarios, and the (project, command) keying fixes the previously-flagged parallel-worktree and different-command concurrent-call races. The remaining gap is two concurrent Bash calls that happen to use the exact same command string — the second post-hook deletes the shared snapshot file and the first post-hook exits without recording its writes, leaving those paths unlogged and blocked at commit time. The tool_use_id field in hook stdin JSON would eliminate this entirely. snapshot-pre-bash.sh — the CMD_HASH snapshot-key derivation is the only remaining concern; track-bash-writes.sh and settings.json look correct. Important Files Changed
Sequence DiagramsequenceDiagram
participant CC as Claude Code
participant Pre as snapshot-pre-bash.sh
participant Bash as Bash tool
participant Post as track-bash-writes.sh
participant Log as session-edits.log
participant Guard as guard-git.sh
CC->>Pre: PreToolUse (stdin JSON w/ command)
Pre->>Pre: skip read-only commands
Pre->>Pre: hash(PROJECT_DIR) + hash(COMMAND)
Pre->>Pre: "git status --porcelain → /tmp/snapshot-{P}-{C}.txt"
CC->>Bash: Execute command (e.g. sed -i, npm build)
Bash-->>CC: Result
CC->>Post: PostToolUse (same stdin JSON)
Post->>Post: derive same snapshot path
Post->>Post: git status --porcelain (AFTER)
Post->>Post: comm -13 BEFORE AFTER → NEW_PATHS
Post->>Log: append TIMESTAMP path for each new path
Post->>Post: rm snapshot file
CC->>Guard: PreToolUse (git commit ...)
Guard->>Log: read EDITED_FILES
Guard->>Guard: compare staged files vs EDITED_FILES
Guard-->>CC: allow or deny commit
Reviews (6): Last reviewed commit: "fix(hooks): remove node -e/-p from read-..." | Re-trigger Greptile |
…by command hash Three correctness gaps addressed in the snapshot-pre-bash / track-bash-writes hook pair: 1. echo, printf, find, and awk were listed as read-only skip candidates but all four can write files (echo/printf via redirections, find via -exec/-delete, awk via getline/redirection). Remove them from the skip list so their file writes are captured in session-edits.log. 2. The snapshot file was keyed only by project root hash, causing a race when Claude Code issues multiple Bash tool calls in parallel: call B's pre-hook would overwrite call A's snapshot before A's post-hook ran, silently dropping A's file writes. Fix by including a hash of the command string in the filename so each concurrent call gets a distinct snapshot file.
|
Addressed Claude's feedback: the P1 concern (echo/printf/find/awk in the skip list) was fixed in commit 12364b6 — all four were removed from the read-only skip list since all can write files via redirections or -exec/-delete. The snapshot file path now also includes a CMD_HASH to handle concurrent parallel Bash calls (P2 concern). The minor observations (subprocess overhead, C-escape sequences in paths, mixed PR scope) are noted; the subprocess approach uses the already-required node runtime so adds no new dependency, and the C-escape edge case degrades to double-logging which is benign. |
|
Fixed in commit c3d4722: removed |
`guard-git.sh` was blocking commits for files legitimately modified by Bash commands (`sed -i`, `printf`, redirects, build tools) because `session-edits.log` only recorded Edit/Write tool calls and `mv`/`cp`.
Adds a pre/post Bash hook pair that snapshots `git status --porcelain` before each Bash tool call and diffs after, logging any newly modified or created files to `session-edits.log`. This is robust to any Bash command regardless of how it writes files.
What changed
.claude/hooks/snapshot-pre-bash.sh(new, PreToolUse Bash): captures `git status --porcelain` to a per-worktree temp file in `/tmp` before each Bash call. Skips known read-only commands (ls, git log, git status, grep, etc.) to avoid unnecessary overhead..claude/hooks/track-bash-writes.sh(new, PostToolUse Bash): reads the snapshot, diffs against current porcelain output using `comm -13`, and appends any newly dirty paths to `.claude/session-edits.log` in the same timestamped format as other tracking hooks..claude/settings.json: wires `snapshot-pre-bash.sh` as the first PreToolUse Bash entry (before `guard-git.sh`) and `track-bash-writes.sh` as the first PostToolUse Bash entry.Key properties
Closes #1457