chore(config): add git-lock-guard PreToolUse hook
Clears stale .git/index.lock files before git write commands. Claude Code's internal git status polling leaves orphan locks (anthropics/claude-code#11005) that block add/commit/merge/push. The hook checks lsof/fuser before removing — only stale locks are cleared, never locks held by live processes. Read-only git commands are skipped entirely. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# git-lock-guard: PreToolUse hook that clears stale git index.lock files
|
||||
# before git write operations.
|
||||
#
|
||||
# Claude Code's internal git status polling creates stale lock files that
|
||||
# persist after the process exits (anthropics/claude-code#11005). This hook
|
||||
# detects and removes them before LLM-initiated git commands run.
|
||||
#
|
||||
# Only acts on git write commands (add, commit, merge, push, etc.).
|
||||
# Read-only commands (status, log, diff, show) are skipped.
|
||||
set -uo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
# Only act on Bash tool calls
|
||||
TOOL_NAME=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" 2>/dev/null)
|
||||
[ "$TOOL_NAME" = "Bash" ] || exit 0
|
||||
|
||||
COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null)
|
||||
|
||||
# Only act on git write commands — skip read-only operations
|
||||
case "$COMMAND" in
|
||||
git\ add*|git\ commit*|git\ merge*|git\ push*|git\ pull*|\
|
||||
git\ checkout*|git\ stash*|git\ rebase*|git\ reset*|\
|
||||
git\ cherry-pick*|git\ rm*|git\ mv*|git\ fetch*)
|
||||
;;
|
||||
*)
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
# Resolve the git directory for the current working directory
|
||||
CWD=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('cwd',''))" 2>/dev/null)
|
||||
[ -n "$CWD" ] || exit 0
|
||||
|
||||
# Find the .git directory (handles both regular repos and worktrees)
|
||||
GIT_DIR=$(git -C "$CWD" rev-parse --git-dir 2>/dev/null) || exit 0
|
||||
|
||||
# For worktrees, also check the common git dir (shared index operations)
|
||||
GIT_COMMON_DIR=$(git -C "$CWD" rev-parse --git-common-dir 2>/dev/null) || GIT_COMMON_DIR="$GIT_DIR"
|
||||
|
||||
for DIR in "$GIT_DIR" "$GIT_COMMON_DIR"; do
|
||||
LOCK="$DIR/index.lock"
|
||||
[ -f "$LOCK" ] || continue
|
||||
|
||||
# Check if any process holds the lock
|
||||
if command -v lsof &>/dev/null; then
|
||||
if lsof "$LOCK" &>/dev/null; then
|
||||
# Lock is held by a live process — don't touch it
|
||||
continue
|
||||
fi
|
||||
elif command -v fuser &>/dev/null; then
|
||||
if fuser "$LOCK" &>/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Stale lock — remove it
|
||||
rm -f "$LOCK" 2>/dev/null
|
||||
done
|
||||
|
||||
# Always allow the command to proceed
|
||||
exit 0
|
||||
@@ -80,5 +80,19 @@
|
||||
"Bash(git clean -f *)",
|
||||
"Bash(rm -rf *)"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": ".claude/hooks/git-lock-guard.sh",
|
||||
"timeout": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user