Version Control Your Claude Code Config
Three files, five minutes, and you never lose a setting again.
Initialized empty Git repository in ~/.claude/.git/
If you're using Claude Code seriously, your ~/.claude/ directory is doing a lot of work. Settings, hooks, skills, commands, global instructions. It accumulates. You tweak things, enable features, add hooks, forget what you changed last week.
git init. That's it. That's the move.
Your ~/.claude/ directory becomes a version-controlled config repo. Every change is a commit with a real diff. You get full history, rollback, and a changelog that writes itself. Three files, five minutes.
What's in ~/.claude/ and What Matters
The directory is 2+ GB. Most of it is session transcripts, caches, plugin installs, telemetry. You don't care about any of that. Here's what you actually want to track:
| File | What it is |
|---|---|
settings.json |
Your main config: hooks, permissions, plugins, env vars |
CLAUDE.md |
Global instructions loaded every session |
commands/ |
Custom slash commands |
agents/ |
Custom subagents |
rules/ |
User-level reusable instruction files |
hooks/ |
Hook scripts referenced by settings.json |
skills/ |
Global custom skills |
projects/*/memory/ |
Per-project memories Claude builds across sessions |
Everything else is ephemeral or regenerable. The projects/ directory is mostly session transcripts and tool artifacts (2+ GB), but the memory/ subdirectories inside each project contain small markdown files that shape Claude's behavior. Worth tracking those selectively.
Setup
Init and ignore
cd ~/.claude
git init
Create a .gitignore that ignores everything, then whitelists what matters:
# Ignore everything by default
*
# Keep git metadata for this repo
!.gitignore
# Core user config
!CLAUDE.md
!settings.json
!auto-commit-config.sh
# User-scoped customizations
!commands/
!commands/**
!skills/
!skills/**
!agents/
!agents/**
!rules/
!rules/**
!hooks/
!hooks/**
# Optional: keep per-project auto-memory
!projects/
!projects/*/
!projects/*/memory/
!projects/*/memory/**
~30KB of tracked files. The other GBs stay out of it.
Initial commit
git add -A
git commit -m "Initial commit: track Claude Code configuration"
You already have a changelog.
Auto-commit on change
You could stop here and commit manually. But the whole point is not thinking about it. A launchd plist watches your config files and auto-commits when they change.
Save this to ~/.claude/auto-commit-config.sh
#!/usr/bin/env bash
CLAUDE_DIR="$HOME/.claude"
cd "$CLAUDE_DIR" || exit 1
# Nothing to commit?
if git diff --quiet && git diff --cached --quiet && \
[ -z "$(git ls-files --others --exclude-standard)" ]; then
exit 0
fi
# Build a commit message from the changed files
CHANGED=$(git diff --name-only; git ls-files --others --exclude-standard)
FILES=$(echo "$CHANGED" | sort -u | tr '\n' ', ' | sed 's/,$//')
git add -A
git commit -m "auto: ${FILES}"
chmod +x ~/.claude/auto-commit-config.sh
Then the launchd plist. Save to ~/Library/LaunchAgents/com.yourname.claude-config-watcher.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.yourname.claude-config-watcher</string>
<key>ProgramArguments</key>
<array>
<string>/Users/YOURUSER/.claude/auto-commit-config.sh</string>
</array>
<key>WatchPaths</key>
<array>
<string>/Users/YOURUSER/.claude/settings.json</string>
<string>/Users/YOURUSER/.claude/CLAUDE.md</string>
<string>/Users/YOURUSER/.claude/hooks</string>
<string>/Users/YOURUSER/.claude/skills</string>
<string>/Users/YOURUSER/.claude/commands</string>
<string>/Users/YOURUSER/.claude/agents</string>
<string>/Users/YOURUSER/.claude/rules</string>
</array>
<key>StartInterval</key>
<integer>60</integer>
<key>StandardOutPath</key>
<string>/tmp/claude-config-watcher.log</string>
<key>StandardErrorPath</key>
<string>/tmp/claude-config-watcher.log</string>
</dict>
</plist>
Replace YOURUSER with your username. Load it:
launchctl bootstrap gui/$(id -u) \
~/Library/LaunchAgents/com.yourname.claude-config-watcher.plist
On older macOS versions, launchctl load may still work. If bootstrap says the service is already loaded, unload it first:
launchctl bootout gui/$(id -u) \
~/Library/LaunchAgents/com.yourname.claude-config-watcher.plist
It survives reboots. No background process to manage. WatchPaths triggers commits when config files change. StartInterval polls every 60 seconds to catch changes in nested directories like projects/*/memory/ that WatchPaths can't see.
Add a secrets scanner
If you ever push this repo to a remote, or even just as a safety net for local commits, add a pre-commit hook that blocks secrets. gitleaks maintains a database of hundreds of secret patterns so you don't have to roll your own regex:
brew install gitleaks
cat > ~/.claude/.git/hooks/pre-commit << HOOK
#!/usr/bin/env bash
$(which gitleaks) git --pre-commit --staged
HOOK
chmod +x ~/.claude/.git/hooks/pre-commit
The auto-commit script will silently fail if a secret is detected, which is what you want.
Verify
Change a setting. Wait a few seconds.
git -C ~/.claude log --oneline
a1b2c3d auto: settings.json
e015a9c Initial commit: track Claude Code configuration
That's your changelog.
What This Gets You
# Full history
git -C ~/.claude log --oneline
# What exactly changed in settings, with diffs
git -C ~/.claude log -p -- settings.json
# When did I add that hook?
git -C ~/.claude log -- hooks/
# What did my config look like last Tuesday?
git -C ~/.claude log --before="last tuesday" -1 -p
# Roll back the last change
git -C ~/.claude revert HEAD
You know that moment where something breaks and you're not sure what you changed? That's gone. Every setting tweak, every hook edit, every command, agent, or skill modification: timestamped with a diff.
Linux
Same idea, swap launchd for inotifywait:
inotifywait -m -r -e modify,create,delete \
~/.claude/settings.json \
~/.claude/CLAUDE.md \
~/.claude/hooks/ \
~/.claude/skills/ \
~/.claude/commands/ \
~/.claude/agents/ \
~/.claude/rules/ \
~/.claude/projects/ \
--exclude '\.jsonl$' |
while read; do
sleep 2
cd ~/.claude || continue
git add -A
if ! git diff --cached --quiet; then
git commit -m "auto: config change"
fi
done
The -r flag watches recursively, and --exclude '\.jsonl$' skips session transcripts so you're not firing on every conversation turn.
Wrap it in a systemd service and you're set.
Have Claude Do This
Tell Claude to read the raw markdown and follow the instructions.
Read https://gist.githubusercontent.com/aviflombaum/cc88ec0e7d08e602b1f983280d7759f5/raw/92899926cc193c74f5d1cc8ee785bdeab3d51a14/version-control-claude-code-config.md and set it up for me.