Auto Changelog Appender triggers after git commit commands and automatically maintains your CHANGELOG.md. It extracts the commit message, classifies the change type (feat, fix, refactor, docs, etc.) from conventional commit prefixes, and appends a formatted entry under the current version's Unreleased section. It groups entries by type (Added, Fixed, Changed, etc.) following the Keep a Changelog convention. If no CHANGELOG.md exists, it creates one with proper formatting. This eliminates the tedious manual work of maintaining changelogs while ensuring every commit is documented.
#!/bin/bash
# Auto Changelog Appender Hook
# Maintains CHANGELOG.md from commit messages
# Only trigger on Bash tool with git commit
if [[ "$TOOL_NAME" != "Bash" ]] || ! echo "$TOOL_INPUT" | grep -q "git commit"; then
exit 0
fi
# Verify commit actually happened
LAST_COMMIT=$(git log -1 --format="%H %s" 2>/dev/null)
if [ -z "$LAST_COMMIT" ]; then
exit 0
fi
COMMIT_MSG=$(echo "$LAST_COMMIT" | cut -d' ' -f2-)
COMMIT_HASH=$(echo "$LAST_COMMIT" | cut -d' ' -f1 | cut -c1-7)
TODAY=$(date +%Y-%m-%d)
CHANGELOG="CHANGELOG.md"
# Classify commit type
classify_commit() {
local msg="$1"
case "$msg" in
feat:*|feat(*) echo "Added" ;;
fix:*|fix(*) echo "Fixed" ;;
docs:*|docs(*) echo "Documentation" ;;
refactor:*|refactor(*) echo "Changed" ;;
perf:*|perf(*) echo "Performance" ;;
test:*|test(*) echo "Testing" ;;
chore:*|chore(*) echo "Maintenance" ;;
BREAKING*|!:*) echo "Breaking Changes" ;;
*) echo "Changed" ;;
esac
}
# Strip conventional commit prefix
clean_message() {
echo "$1" | sed -E 's/^(feat|fix|docs|refactor|perf|test|chore)(\([^)]*\))?!?:\s*//'
}
CATEGORY=$(classify_commit "$COMMIT_MSG")
CLEAN_MSG=$(clean_message "$COMMIT_MSG")
# Create CHANGELOG if it doesn't exist
if [ ! -f "$CHANGELOG" ]; then
cat > "$CHANGELOG" << 'TMPL'
# Changelog
All notable changes to this project will be documented in this file.
## [Unreleased]
TMPL
fi
# Check if Unreleased section exists
if ! grep -q "## \[Unreleased\]" "$CHANGELOG"; then
sed -i.bak '3a\n## [Unreleased]\n' "$CHANGELOG"
rm -f "$CHANGELOG.bak"
fi
# Check if category subsection exists under Unreleased
UNRELEASED_LINE=$(grep -n "## \[Unreleased\]" "$CHANGELOG" | head -1 | cut -d: -f1)
NEXT_VERSION_LINE=$(awk "NR>$UNRELEASED_LINE && /^## \[/{print NR; exit}" "$CHANGELOG")
NEXT_VERSION_LINE=${NEXT_VERSION_LINE:-$(wc -l < "$CHANGELOG")}
ENTRY="- $CLEAN_MSG (\`$COMMIT_HASH\`)"
# Insert entry under the appropriate category
if grep -q "### $CATEGORY" "$CHANGELOG"; then
CATEGORY_LINE=$(grep -n "### $CATEGORY" "$CHANGELOG" | head -1 | cut -d: -f1)
sed -i.bak "${CATEGORY_LINE}a\
$ENTRY" "$CHANGELOG"
else
sed -i.bak "/## \[Unreleased\]/a\
\\n### $CATEGORY\
$ENTRY" "$CHANGELOG"
fi
rm -f "$CHANGELOG.bak"
echo "Changelog updated: [$CATEGORY] $CLEAN_MSG"
exit 0
Add this hook to your Claude Code settings or .claude/settings.json to activate.
Terminal Preview
Auto Changelog Appender
About Auto Changelog Appender
Claude Code hooks let you run custom shell commands automatically in response to specific events during Claude's operation. Auto Changelog Appender is a Post-Tool hook at the Advanced level that automates tasks at key moments in your development workflow, reducing manual steps and enforcing consistency across your team.