BS
BleepingSwift
Published on
5 min read

> Claude Code Auto Commit: How to Automatically Commit Changes as You Go

Authors
  • avatar
    Name
    Mick MacCallum
    Twitter
    @0x7fs

One of the best things about working with Claude Code is watching it churn through a multi-file refactor or feature implementation while you sip your coffee. The problem is that if something goes wrong halfway through, you might lose a chunk of good work because nothing was committed along the way.

The fix is to set up Claude Code auto commit behavior so changes get saved to git incrementally. There are a few ways to do this, ranging from simple instructions to fully automated hooks.

The CLAUDE.md Approach

The simplest way to get Claude Code to commit as it goes is to just tell it to. Add instructions to your project's CLAUDE.md file:

## When to commit

- Create commits after completing each logical unit of work.
- Do not push to the remote repository unless asked.
- Use conventional commit messages (e.g. "feat:", "fix:", "refactor:").

Claude Code reads this file at the start of every session and follows the instructions throughout. It will use git add and git commit on its own whenever it finishes a meaningful piece of work.

This approach is flexible because Claude decides when a "logical unit" is complete. It won't commit after every single line change, but it also won't wait until the very end. You can be more or less specific depending on your preference. Something like "commit after every file change" will produce more granular history, while "commit after completing each task" keeps things tidier.

The tradeoff is that this relies on Claude's judgment. It usually gets this right, but it's not guaranteed to commit at exactly the moments you'd want. For something more deterministic, hooks are the answer.

Auto-Committing with Hooks

Claude Code has a hooks system that lets you run shell commands automatically at specific points in its lifecycle. You can use this to trigger a git commit every time Claude edits a file.

Add this to your .claude/settings.json (create the file if it doesn't exist):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "cd \"$CLAUDE_PROJECT_DIR\" && git add -A && git diff-index --quiet HEAD || git commit -m \"wip\" 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

This fires after every Write or Edit tool call. The git diff-index --quiet HEAD check prevents empty commits when nothing actually changed, and the || true at the end ensures that a failed commit doesn't block Claude from continuing its work.

The commit messages here are just "wip" since these are incremental saves. You can always squash or rewrite them later with an interactive rebase. If you want slightly more descriptive messages, you can parse the hook input to extract the file path:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=$(echo $0 | jq -r '.tool_input.file_path // empty' 2>/dev/null | xargs basename 2>/dev/null); cd \"$CLAUDE_PROJECT_DIR\" && git add -A && git diff-index --quiet HEAD || git commit -m \"Update ${FILE:-files}\" 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

One thing to be aware of: PostToolUse hooks run synchronously by default, meaning Claude waits for the commit to finish before moving on. For small repos this is barely noticeable, but if your git operations are slow you can add "async": true to run the commit in the background:

{
  "type": "command",
  "command": "cd \"$CLAUDE_PROJECT_DIR\" && git add -A && git diff-index --quiet HEAD || git commit -m \"wip\" 2>/dev/null || true",
  "async": true,
  "timeout": 30
}

Using a Stop Hook Instead

If committing after every file edit feels too granular, you can commit only when Claude finishes a full response. The Stop hook fires at the end of each turn:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "cd \"$CLAUDE_PROJECT_DIR\" && git add -A && git diff-index --quiet HEAD || git commit -m \"checkpoint\" 2>/dev/null || true"
          }
        ]
      }
    ]
  }
}

This produces fewer commits since Claude often edits multiple files in a single turn. You get a checkpoint after each back-and-forth, which is usually a natural breakpoint anyway.

Don't Forget Permissions

For any of these approaches to work without constant interruptions, you need to make sure Claude Code is allowed to run git commands. Add these to your permission allow list in ~/.claude/settings.json:

{
  "permissions": {
    "allow": [
      "Bash(git add *)",
      "Bash(git commit *)",
      "Bash(git status*)",
      "Bash(git diff*)",
      "Bash(git log*)"
    ]
  }
}

Notice that git push is intentionally left out. Auto-committing locally is safe since you can always amend, squash, or reset. Auto-pushing is a different story, and you probably want to keep that behind an approval prompt.

Which Approach Should You Use?

The CLAUDE.md approach is the easiest to set up and produces the cleanest commit history since Claude writes meaningful commit messages and chooses sensible breakpoints. It's what I'd recommend starting with.

If you want something more deterministic, the Stop hook is a good middle ground. You get automatic commits without the noise of one per file edit.

The PostToolUse hook is the most aggressive option. It's useful when you're doing large refactors and want the ability to roll back to any intermediate state, but you'll probably want to squash the commits afterward.

You can also combine approaches. Use CLAUDE.md instructions for meaningful commits and a Stop hook as a safety net. Claude is smart enough not to double-commit when it just committed a moment ago.

subscribe.sh

// Stay Updated

Get notified when I publish new tutorials on Swift, SwiftUI, and iOS development. No spam, unsubscribe anytime.

>

By subscribing, you agree to our Privacy Policy.