@@ -23,18 +23,27 @@ if [ -z "$COMMAND" ]; then
2323 exit 0
2424fi
2525
26- # Skip read-only commands that can never write files — reduces snapshot overhead
27- # for the most common Bash calls (ls, cat, grep, git log, git status, etc.).
26+ # Skip commands that can NEVER write files — reduces snapshot overhead for the
27+ # most common read-only Bash calls. Only include commands that have no
28+ # write-capable flags/modes at all. Notably absent:
29+ # - echo, printf — write files via shell redirections (echo … > file)
30+ # - find — can write via -exec sed -i, -exec cp, -delete, etc.
31+ # - awk — can write via redirection or getline
32+ # - node -e/-p — can write files via the Node.js fs module
2833# sed is intentionally NOT in this list because `sed -i` modifies files in-place.
29- if echo " $COMMAND " | grep -qE ' ^\s*(ls|cat|head|tail|grep|find| git\s+(log|status|diff|show|branch|remote|fetch|rev-parse|stash\s+list|ls-files|blame|describe|tag|config\s+--get)|gh\s+(pr|issue|repo)\s+(view|list|status)|echo|printf| pwd|which|node\s+-e|node\s+-p| npx\s+--version|wc|sort|uniq|awk )\b' ; then
34+ if echo " $COMMAND " | grep -qE ' ^\s*(ls|cat|head|tail|grep|git\s+(log|status|diff|show|branch|remote|fetch|rev-parse|stash\s+list|ls-files|blame|describe|tag|config\s+--get)|gh\s+(pr|issue|repo)\s+(view|list|status)|pwd|which|npx\s+--version|wc|sort|uniq)\b' ; then
3035 exit 0
3136fi
3237
3338# Resolve the project root (worktree-aware — each worktree has its own .claude/)
3439PROJECT_DIR=$( git rev-parse --show-toplevel 2> /dev/null) || PROJECT_DIR=" ${CLAUDE_PROJECT_DIR:- .} "
3540
36- # Key the snapshot file to the project root so parallel worktrees don't collide.
37- # Use a simple hash of the path — just enough to be unique per worktree.
41+ # Key the snapshot file to (project root, command) so concurrent Bash calls
42+ # within the same session don't overwrite each other's baseline.
43+ # Claude Code can issue multiple Bash tool calls in parallel; using just the
44+ # project hash would mean call B's pre-hook overwrites call A's snapshot before
45+ # A's post-hook runs, silently dropping A's file writes from session-edits.log.
46+ # Including a hash of the command makes each concurrent call use a distinct file.
3847PROJECT_HASH=$( echo " $PROJECT_DIR " | node -e "
3948 const crypto = require('crypto');
4049 let d='';
@@ -44,7 +53,16 @@ PROJECT_HASH=$(echo "$PROJECT_DIR" | node -e "
4453 });
4554" 2> /dev/null) || PROJECT_HASH=" default"
4655
47- SNAPSHOT_FILE=" /tmp/claude-bash-snapshot-${PROJECT_HASH} .txt"
56+ CMD_HASH=$( echo " $COMMAND " | node -e "
57+ const crypto = require('crypto');
58+ let d='';
59+ process.stdin.on('data',c=>d+=c);
60+ process.stdin.on('end',()=>{
61+ process.stdout.write(crypto.createHash('sha1').update(d.trim()).digest('hex').slice(0,8));
62+ });
63+ " 2> /dev/null) || CMD_HASH=" default"
64+
65+ SNAPSHOT_FILE=" /tmp/claude-bash-snapshot-${PROJECT_HASH} -${CMD_HASH} .txt"
4866
4967# Capture current git status --porcelain.
5068# Lines look like: "XY filename" or "XY orig -> dest" (rename).
0 commit comments