avi.nyc

Version Control Your Claude Code Config

Three files, five minutes, and you never lose a setting again.

~/.claude
$ git init
Initialized empty Git repository in ~/.claude/.git/
Scroll to continue

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

1

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.

2

Initial commit

git add -A
git commit -m "Initial commit: track Claude Code configuration"

You already have a changelog.

3

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.

4

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.

5

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.