Loading...
Automatically commits all changes with a summary when Claude Code session ends
{
"hookConfig": {
"hooks": {
"stop": {
"script": "./.claude/hooks/git-auto-commit-on-stop.sh"
}
}
},
"scriptContent": "#!/usr/bin/env bash\n\necho \"💾 Checking for changes to auto-commit...\" >&2\n\n# Check if we're in a git repository\nif ! git rev-parse --git-dir > /dev/null 2>&1; then\n echo \"⚠️ Not in a git repository - skipping auto-commit\" >&2\n exit 0\nfi\n\n# Check if git is configured\nif ! git config user.email > /dev/null 2>&1 || ! git config user.name > /dev/null 2>&1; then\n echo \"⚠️ Git user not configured - skipping auto-commit\" >&2\n echo \"💡 Run: git config --global user.email 'your@email.com'\" >&2\n echo \"💡 Run: git config --global user.name 'Your Name'\" >&2\n exit 0\nfi\n\n# Get current timestamp\nTIMESTAMP=$(date +\"%Y-%m-%d %H:%M:%S\")\nISO_TIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%SZ\" 2>/dev/null || date +\"%Y-%m-%d %H:%M:%S UTC\")\n\n# Get current branch\nCURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo \"unknown\")\n\n# Check for uncommitted changes\nif [ -z \"$(git status --porcelain 2>/dev/null)\" ]; then\n echo \"✨ No changes to commit - repository is clean\" >&2\n exit 0\nfi\n\necho \"📊 Analyzing changes for auto-commit...\" >&2\n\n# Get status information\nUNTRACKED_FILES=$(git status --porcelain 2>/dev/null | grep '^??' | wc -l | xargs)\nMODIFIED_FILES=$(git status --porcelain 2>/dev/null | grep '^.M' | wc -l | xargs)\nADDED_FILES=$(git status --porcelain 2>/dev/null | grep '^A' | wc -l | xargs)\nDELETED_FILES=$(git status --porcelain 2>/dev/null | grep '^.D' | wc -l | xargs)\nRENAMED_FILES=$(git status --porcelain 2>/dev/null | grep '^R' | wc -l | xargs)\n\necho \"📋 Change summary:\" >&2\necho \" Branch: $CURRENT_BRANCH\" >&2\necho \" Untracked: $UNTRACKED_FILES files\" >&2\necho \" Modified: $MODIFIED_FILES files\" >&2\necho \" Added: $ADDED_FILES files\" >&2\necho \" Deleted: $DELETED_FILES files\" >&2\necho \" Renamed: $RENAMED_FILES files\" >&2\n\n# Check for sensitive files before committing\necho \"🔒 Checking for sensitive files...\" >&2\nSENSITIVE_PATTERNS=(\n \"\\.env\"\n \"\\.env\\.*\"\n \"*secret*\"\n \"*password*\"\n \"*key*\"\n \"id_rsa\"\n \"id_ed25519\"\n \"*.pem\"\n \"*.p12\"\n \"*.pfx\"\n)\n\nSENSITIVE_FOUND=false\nfor pattern in \"${SENSITIVE_PATTERNS[@]}\"; do\n if git status --porcelain 2>/dev/null | grep -q \"$pattern\"; then\n SENSITIVE_FOUND=true\n echo \"⚠️ Potentially sensitive file detected: $pattern\" >&2\n fi\ndone\n\n# Check if .gitignore exists and is respected\nif [ ! -f \".gitignore\" ]; then\n echo \"💡 Consider creating a .gitignore file to exclude unwanted files\" >&2\nfi\n\n# Option to skip auto-commit if environment variable is set\nif [ \"$SKIP_AUTO_COMMIT\" = \"true\" ]; then\n echo \"⏭️ Auto-commit skipped (SKIP_AUTO_COMMIT=true)\" >&2\n exit 0\nfi\n\n# Warn about sensitive files but don't block (user choice)\nif [ \"$SENSITIVE_FOUND\" = true ]; then\n echo \"⚠️ Sensitive files detected - proceeding with caution\" >&2\n echo \"💡 Set SKIP_AUTO_COMMIT=true to disable auto-commits\" >&2\nfi\n\n# Add all changes (respecting .gitignore)\necho \"📥 Staging changes for commit...\" >&2\ngit add -A\n\n# Double-check that we have staged changes\nif [ -z \"$(git diff --cached --name-only)\" ]; then\n echo \"ℹ️ No changes staged after git add - nothing to commit\" >&2\n exit 0\nfi\n\n# Calculate detailed statistics\necho \"📊 Calculating commit statistics...\" >&2\n\nFILES_CHANGED=$(git diff --cached --numstat | wc -l | xargs)\nINSERTIONS=0\nDELETIONS=0\n\n# Calculate insertions and deletions more reliably\nif command -v awk &> /dev/null; then\n STATS=$(git diff --cached --numstat | awk '{insertions+=$1; deletions+=$2} END {print insertions \" \" deletions}')\n read -r INSERTIONS DELETIONS <<< \"$STATS\"\nelse\n # Fallback method\n INSERTIONS=$(git diff --cached --stat | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' | paste -sd+ | bc 2>/dev/null || echo '0')\n DELETIONS=$(git diff --cached --stat | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' | paste -sd+ | bc 2>/dev/null || echo '0')\nfi\n\n# Generate commit message\nCOMMIT_MSG=\"🤖 Claude Code auto-commit: Session ended\"\n\n# Add detailed commit body\nCOMMIT_BODY=$(cat <<EOF\n\nSession Summary:\n- Branch: $CURRENT_BRANCH\n- Files changed: $FILES_CHANGED\n- Insertions: +$INSERTIONS\n- Deletions: -$DELETIONS\n- Timestamp: $TIMESTAMP\n\nChanges by type:\n- Modified files: $MODIFIED_FILES\n- New files: $UNTRACKED_FILES\n- Deleted files: $DELETED_FILES\n- Renamed files: $RENAMED_FILES\n\n🤖 Generated with Claude Code\nEOF\n)\n\n# Show what will be committed\necho \"📝 Files to be committed:\" >&2\ngit diff --cached --name-status | head -10 | while read status file; do\n case $status in\n A) echo \" ✅ Added: $file\" >&2 ;;\n M) echo \" ✏️ Modified: $file\" >&2 ;;\n D) echo \" ❌ Deleted: $file\" >&2 ;;\n R*) echo \" 🔄 Renamed: $file\" >&2 ;;\n *) echo \" 📄 $status: $file\" >&2 ;;\n esac\ndone\n\nif [ \"$FILES_CHANGED\" -gt 10 ]; then\n echo \" ... and $((FILES_CHANGED - 10)) more files\" >&2\nfi\n\necho \"\" >&2\necho \"💾 Creating auto-commit...\" >&2\n\n# Create the commit\nif echo \"$COMMIT_BODY\" | git commit -F -; then\n echo \"✅ Auto-commit successful!\" >&2\n \n # Show commit info\n COMMIT_HASH=$(git rev-parse --short HEAD)\n echo \"📝 Commit: $COMMIT_HASH\" >&2\n echo \"🌿 Branch: $CURRENT_BRANCH\" >&2\n \n # Check if we should push (optional)\n if [ \"$AUTO_PUSH\" = \"true\" ]; then\n echo \"📤 Auto-pushing to remote...\" >&2\n if git push 2>/dev/null; then\n echo \"✅ Pushed to remote successfully\" >&2\n else\n echo \"⚠️ Push failed - commit created locally\" >&2\n echo \"💡 Run 'git push' manually when ready\" >&2\n fi\n else\n echo \"💡 Set AUTO_PUSH=true to automatically push commits\" >&2\n fi\n \nelse\n echo \"❌ Auto-commit failed\" >&2\n echo \"💡 You may need to resolve conflicts or check git status\" >&2\n exit 1\nfi\n\necho \"\" >&2\necho \"📋 Auto-Commit Summary:\" >&2\necho \" ✅ $FILES_CHANGED files committed\" >&2\necho \" 📈 +$INSERTIONS insertions, -$DELETIONS deletions\" >&2\necho \" ⏰ $TIMESTAMP\" >&2\necho \"\" >&2\necho \"💡 Git Auto-Commit Tips:\" >&2\necho \" • Set SKIP_AUTO_COMMIT=true to disable\" >&2\necho \" • Set AUTO_PUSH=true to auto-push commits\" >&2\necho \" • Review commits with 'git log --oneline'\" >&2\necho \" • Use .gitignore to exclude sensitive files\" >&2\n\nexit 0"
}.claude/hooks/~/.claude/hooks/{
"hooks": {
"stop": {
"script": "./.claude/hooks/git-auto-commit-on-stop.sh"
}
}
}#!/usr/bin/env bash
echo "💾 Checking for changes to auto-commit..." >&2
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo "⚠️ Not in a git repository - skipping auto-commit" >&2
exit 0
fi
# Check if git is configured
if ! git config user.email > /dev/null 2>&1 || ! git config user.name > /dev/null 2>&1; then
echo "⚠️ Git user not configured - skipping auto-commit" >&2
echo "💡 Run: git config --global user.email 'your@email.com'" >&2
echo "💡 Run: git config --global user.name 'Your Name'" >&2
exit 0
fi
# Get current timestamp
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
ISO_TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || date +"%Y-%m-%d %H:%M:%S UTC")
# Get current branch
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
# Check for uncommitted changes
if [ -z "$(git status --porcelain 2>/dev/null)" ]; then
echo "✨ No changes to commit - repository is clean" >&2
exit 0
fi
echo "📊 Analyzing changes for auto-commit..." >&2
# Get status information
UNTRACKED_FILES=$(git status --porcelain 2>/dev/null | grep '^??' | wc -l | xargs)
MODIFIED_FILES=$(git status --porcelain 2>/dev/null | grep '^.M' | wc -l | xargs)
ADDED_FILES=$(git status --porcelain 2>/dev/null | grep '^A' | wc -l | xargs)
DELETED_FILES=$(git status --porcelain 2>/dev/null | grep '^.D' | wc -l | xargs)
RENAMED_FILES=$(git status --porcelain 2>/dev/null | grep '^R' | wc -l | xargs)
echo "📋 Change summary:" >&2
echo " Branch: $CURRENT_BRANCH" >&2
echo " Untracked: $UNTRACKED_FILES files" >&2
echo " Modified: $MODIFIED_FILES files" >&2
echo " Added: $ADDED_FILES files" >&2
echo " Deleted: $DELETED_FILES files" >&2
echo " Renamed: $RENAMED_FILES files" >&2
# Check for sensitive files before committing
echo "🔒 Checking for sensitive files..." >&2
SENSITIVE_PATTERNS=(
"\.env"
"\.env\.*"
"*secret*"
"*password*"
"*key*"
"id_rsa"
"id_ed25519"
"*.pem"
"*.p12"
"*.pfx"
)
SENSITIVE_FOUND=false
for pattern in "${SENSITIVE_PATTERNS[@]}"; do
if git status --porcelain 2>/dev/null | grep -q "$pattern"; then
SENSITIVE_FOUND=true
echo "⚠️ Potentially sensitive file detected: $pattern" >&2
fi
done
# Check if .gitignore exists and is respected
if [ ! -f ".gitignore" ]; then
echo "💡 Consider creating a .gitignore file to exclude unwanted files" >&2
fi
# Option to skip auto-commit if environment variable is set
if [ "$SKIP_AUTO_COMMIT" = "true" ]; then
echo "⏭️ Auto-commit skipped (SKIP_AUTO_COMMIT=true)" >&2
exit 0
fi
# Warn about sensitive files but don't block (user choice)
if [ "$SENSITIVE_FOUND" = true ]; then
echo "⚠️ Sensitive files detected - proceeding with caution" >&2
echo "💡 Set SKIP_AUTO_COMMIT=true to disable auto-commits" >&2
fi
# Add all changes (respecting .gitignore)
echo "📥 Staging changes for commit..." >&2
git add -A
# Double-check that we have staged changes
if [ -z "$(git diff --cached --name-only)" ]; then
echo "ℹ️ No changes staged after git add - nothing to commit" >&2
exit 0
fi
# Calculate detailed statistics
echo "📊 Calculating commit statistics..." >&2
FILES_CHANGED=$(git diff --cached --numstat | wc -l | xargs)
INSERTIONS=0
DELETIONS=0
# Calculate insertions and deletions more reliably
if command -v awk &> /dev/null; then
STATS=$(git diff --cached --numstat | awk '{insertions+=$1; deletions+=$2} END {print insertions " " deletions}')
read -r INSERTIONS DELETIONS <<< "$STATS"
else
# Fallback method
INSERTIONS=$(git diff --cached --stat | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' | paste -sd+ | bc 2>/dev/null || echo '0')
DELETIONS=$(git diff --cached --stat | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' | paste -sd+ | bc 2>/dev/null || echo '0')
fi
# Generate commit message
COMMIT_MSG="🤖 Claude Code auto-commit: Session ended"
# Add detailed commit body
COMMIT_BODY=$(cat <<EOF
Session Summary:
- Branch: $CURRENT_BRANCH
- Files changed: $FILES_CHANGED
- Insertions: +$INSERTIONS
- Deletions: -$DELETIONS
- Timestamp: $TIMESTAMP
Changes by type:
- Modified files: $MODIFIED_FILES
- New files: $UNTRACKED_FILES
- Deleted files: $DELETED_FILES
- Renamed files: $RENAMED_FILES
🤖 Generated with Claude Code
EOF
)
# Show what will be committed
echo "📝 Files to be committed:" >&2
git diff --cached --name-status | head -10 | while read status file; do
case $status in
A) echo " ✅ Added: $file" >&2 ;;
M) echo " ✏️ Modified: $file" >&2 ;;
D) echo " ❌ Deleted: $file" >&2 ;;
R*) echo " 🔄 Renamed: $file" >&2 ;;
*) echo " 📄 $status: $file" >&2 ;;
esac
done
if [ "$FILES_CHANGED" -gt 10 ]; then
echo " ... and $((FILES_CHANGED - 10)) more files" >&2
fi
echo "" >&2
echo "💾 Creating auto-commit..." >&2
# Create the commit
if echo "$COMMIT_BODY" | git commit -F -; then
echo "✅ Auto-commit successful!" >&2
# Show commit info
COMMIT_HASH=$(git rev-parse --short HEAD)
echo "📝 Commit: $COMMIT_HASH" >&2
echo "🌿 Branch: $CURRENT_BRANCH" >&2
# Check if we should push (optional)
if [ "$AUTO_PUSH" = "true" ]; then
echo "📤 Auto-pushing to remote..." >&2
if git push 2>/dev/null; then
echo "✅ Pushed to remote successfully" >&2
else
echo "⚠️ Push failed - commit created locally" >&2
echo "💡 Run 'git push' manually when ready" >&2
fi
else
echo "💡 Set AUTO_PUSH=true to automatically push commits" >&2
fi
else
echo "❌ Auto-commit failed" >&2
echo "💡 You may need to resolve conflicts or check git status" >&2
exit 1
fi
echo "" >&2
echo "📋 Auto-Commit Summary:" >&2
echo " ✅ $FILES_CHANGED files committed" >&2
echo " 📈 +$INSERTIONS insertions, -$DELETIONS deletions" >&2
echo " ⏰ $TIMESTAMP" >&2
echo "" >&2
echo "💡 Git Auto-Commit Tips:" >&2
echo " • Set SKIP_AUTO_COMMIT=true to disable" >&2
echo " • Set AUTO_PUSH=true to auto-push commits" >&2
echo " • Review commits with 'git log --oneline'" >&2
echo " • Use .gitignore to exclude sensitive files" >&2
exit 0Hook creates commits even when no meaningful changes made
git status --porcelain check shows temp files. Update .gitignore excluding: '.DS_Store', '.swp', 'node_modules/'. Or add file count threshold: 'if [ "$MODIFIED_FILES" -lt 2 ]; then exit 0; fi'.
Sensitive files (.env) committed despite pattern detection warnings
Pattern match non-blocking by design. Add hard block: 'if [ "$SENSITIVE_FOUND" = true ]; then exit 1; fi' before git add. Or use git-secrets: 'git secrets --scan' pre-commit.
Auto-commit fails with empty commit message or malformed body
COMMIT_BODY uses cat with EOF delimiter requiring proper quoting. Replace with: 'git commit -m "Auto-commit: $TIMESTAMP" -m "Files: $FILES_CHANGED" -m "Branch: $CURRENT_BRANCH"' avoiding heredoc issues.
SKIP_AUTO_COMMIT environment variable ignored when set
Variable not exported to subprocess. Use: 'export SKIP_AUTO_COMMIT=true' before Claude session. Or add to shell profile: 'echo "export SKIP_AUTO_COMMIT=true" >> ~/.bashrc'. Verify: 'env | grep SKIP'.
Statistics show zero insertions/deletions despite file changes
git diff --stat fails on binary files or first commit. Add: '--ignore-all-space --ignore-blank-lines' flags. Or use: 'git diff --numstat | awk "{add+=$1; del+=$2} END {print add, del}"' for accurate counts.
Loading reviews...
Join our community of Claude power users. No spam, unsubscribe anytime.
Automated accessibility testing and compliance checking for web applications following WCAG guidelines
Automatically generates or updates API documentation when endpoint files are modified
Automatically formats code files after Claude writes or edits them using Prettier, Black, or other formatters