Skip to content

Instantly share code, notes, and snippets.

@dmccreary
Created April 11, 2026 18:33
Show Gist options
  • Select an option

  • Save dmccreary/693a3ebacd14bae3add9c5a931e0f011 to your computer and use it in GitHub Desktop.

Select an option

Save dmccreary/693a3ebacd14bae3add9c5a931e0f011 to your computer and use it in GitHub Desktop.
Claude Code hook: auto-commit and push your claude-skills repo whenever Claude Code edits a file inside it. Solves the 'I forgot to push from machine A' problem when you use Claude skills across multiple computers.

Auto-commit Claude skills repo

A small Claude Code hook that automatically commits and pushes any change you make to your claude-skills repo (or whatever repo you store your custom Claude skills in) during a Claude Code session.

The problem this solves

If you keep your Claude skills in a git repo and use them across multiple machines, every edit you make in one Claude Code session needs to be committed and pushed before the other machines can pull it. It is very easy to forget. The result is the same skill in different states on different machines, and the wrong version showing up in a session you wished was running the latest one.

This hook removes the chance of forgetting. Every time Claude Code edits a file inside your skills repo, the change is committed and pushed immediately, with a clear Auto-commit: prefix in the message so you can distinguish it from your manual commits.

What the hook does

Step Behavior
Claude Code calls Edit, Write, or MultiEdit The PostToolUse hook fires
The hook reads tool_input.file_path from stdin
Path is not inside your skills repo Hook exits 0 silently. No effect on other projects.
Path is inside your skills repo cd into the repo, git add only the specific file (not -A, so unrelated WIP is safe), check if anything actually changed
Nothing actually changed Exit 0 silently
Real change Commit with message Auto-commit: update <rel-path> from Claude Code session plus session ID, tool name, timestamp, and a Co-Authored-By: Claude Code trailer
Push succeeds Logged and done
Push fails (offline, conflicts, etc.) Logged, but commit is safe locally — push manually later
Anything fails Hook exits 0 anyway — never blocks Claude Code

Install

1. Drop the script in your hooks directory

mkdir -p ~/.claude/hooks
curl -fsSL https://gist.githubusercontent.com/dmccreary/693a3ebacd14bae3add9c5a931e0f011/raw/auto-commit-claude-skills.sh \
  -o ~/.claude/hooks/auto-commit-claude-skills.sh
chmod +x ~/.claude/hooks/auto-commit-claude-skills.sh

2. Register the hook in ~/.claude/settings.json

Add this entry under hooks.PostToolUse. If PostToolUse already exists, just add a new object to its array — don't replace what's there.

{
  "matcher": "Edit|Write|MultiEdit",
  "hooks": [
    {
      "type": "command",
      "command": "~/.claude/hooks/auto-commit-claude-skills.sh"
    }
  ]
}

A complete settings.json with only this hook configured looks like:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/auto-commit-claude-skills.sh"
          }
        ]
      }
    ]
  }
}

3. Configure the watched repo path (only if yours differs)

The script defaults to $HOME/Documents/ws/claude-skills. If your skills repo lives somewhere else, set CLAUDE_SKILLS_REPO in your shell startup file (~/.zshrc, ~/.bashrc, etc.):

export CLAUDE_SKILLS_REPO="$HOME/code/my-claude-skills"

Restart your terminal (and Claude Code) so the new env var is picked up.

Verify it works

After installing:

  1. Bail-out test — confirm the hook ignores files outside your skills repo:

    echo '{"tool_name":"Edit","tool_input":{"file_path":"/tmp/not-a-skill.txt"},"session_id":"test"}' \
      | bash ~/.claude/hooks/auto-commit-claude-skills.sh
    echo "exit=$?"

    Expected: exit=0 and no output. The hook bailed out silently.

  2. JSON validation — confirm settings.json still parses:

    jq . ~/.claude/settings.json > /dev/null && echo "JSON OK"
  3. End-to-end test — start Claude Code, edit a file inside your skills repo (or just touch the description in a SKILL.md), and watch:

    tail -f ~/.claude/activity-logs/auto-commit-claude-skills.log

    You should see a Committed line and a Pushed line within a second or two of the edit completing.

Where to look when debugging

File What it tells you
~/.claude/activity-logs/auto-commit-claude-skills.log Every commit, push, and failure with timestamps
cd $CLAUDE_SKILLS_REPO && git log --oneline --grep='^Auto-commit:' All hook-generated commits, separated from your manual ones
cd $CLAUDE_SKILLS_REPO && git status Anything still pending locally (e.g., if push has been failing)

Disable

Two ways:

  1. Temporary — comment out the new entry in ~/.claude/settings.json, restart Claude Code.
  2. Permanent — delete the script and remove the entry from settings.json.

Both are reversible in seconds.

Caveats

  1. One commit per edit. If you edit 5 skill files in a single Claude Code session, you get 5 commits. This is intentional — eager commits beat batched commits when the goal is "never forget to push." If commit-history noise becomes a problem, you can interactively rebase later, or fork the script to commit on every edit but only push on Stop (the session-end Claude Code hook).
  2. Pre-commit hooks still run. The script does not pass --no-verify. If your skills repo has a linter or other pre-commit hook, it runs on every auto-commit. That's the right default — never bypass safety checks silently.
  3. Only Edit, Write, and MultiEdit are watched. If something else modifies files in your skills repo (a script you ran via the Bash tool, a file generated by a build step, etc.), the hook does not fire. For those cases, manual git add && git commit && git push is still required.
  4. Push runs synchronously. On a slow network the hook will block Claude Code for the duration of the push. In practice this is usually under one second. If you ever notice it as a lag, change the git push line to git push & to background it.
  5. The hook needs jq. It is preinstalled on macOS via Homebrew if you have gh working, and on most Linux distros via the package manager. brew install jq or apt install jq if needed.

Why I share this

I work on five different machines. Forgetting to push my Claude skills repo from one of them was a recurring source of "wait, why is the new version of my skill not running here?" frustration. This hook ended that class of problem entirely. If you also juggle skills across machines, it might end yours too.

License

Public domain / CC0 / do whatever you want. No warranty.

#!/bin/bash
# Auto-commit and push changes to a Claude skills repo when files inside it
# are edited via Claude Code's Edit, Write, or MultiEdit tools.
#
# Wired into ~/.claude/settings.json as a PostToolUse hook with matcher
# "Edit|Write|MultiEdit". The hook fires after every Edit/Write/MultiEdit
# but only acts if the modified file lives inside the configured repo path
# below — edits in other projects are unaffected.
#
# CONFIGURATION:
# Override the watched repo path by setting CLAUDE_SKILLS_REPO in your
# shell environment, e.g. in ~/.zshrc or ~/.bashrc:
# export CLAUDE_SKILLS_REPO="$HOME/code/my-claude-skills"
# Default: $HOME/Documents/ws/claude-skills
#
# LOGGING:
# All activity (commits, pushes, failures) is logged to:
# ~/.claude/activity-logs/auto-commit-claude-skills.log
#
# FAILURE HANDLING:
# Failures are logged but never block Claude Code (the hook always exits 0).
#
# Source / updates:
# https://gist.github.com/dmccreary/693a3ebacd14bae3add9c5a931e0f011
HOOK_INPUT=$(cat)
TOOL_NAME=$(echo "$HOOK_INPUT" | jq -r '.tool_name // empty')
FILE_PATH=$(echo "$HOOK_INPUT" | jq -r '.tool_input.file_path // empty')
SESSION_ID=$(echo "$HOOK_INPUT" | jq -r '.session_id // empty')
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
REPO="${CLAUDE_SKILLS_REPO:-$HOME/Documents/ws/claude-skills}"
LOG_DIR="$HOME/.claude/activity-logs"
LOG_FILE="$LOG_DIR/auto-commit-claude-skills.log"
mkdir -p "$LOG_DIR"
# Bail out fast if no file_path (defensive — Edit/Write should always have one)
if [ -z "$FILE_PATH" ]; then
exit 0
fi
# Only proceed for files inside the claude-skills repo
case "$FILE_PATH" in
"$REPO"/*) ;;
*) exit 0 ;;
esac
# Bail out if the repo doesn't actually exist or isn't a git repo
if [ ! -d "$REPO/.git" ]; then
echo "[$TIMESTAMP] ERROR: $REPO is not a git repo — skipping" >> "$LOG_FILE"
exit 0
fi
cd "$REPO" || exit 0
REL_PATH="${FILE_PATH#$REPO/}"
# Stage only the specific file that was edited (not -A — we don't want to
# sweep up unrelated WIP the user may have)
if ! git add -- "$FILE_PATH" 2>>"$LOG_FILE"; then
echo "[$TIMESTAMP] git add failed for $REL_PATH" >> "$LOG_FILE"
exit 0
fi
# If nothing actually changed, bail out silently. Edit may have written
# the same content, or the file may already match HEAD.
if git diff --cached --quiet -- "$FILE_PATH" 2>/dev/null; then
exit 0
fi
# Commit with a clear, automated message
COMMIT_MSG="Auto-commit: update $REL_PATH from Claude Code session
Session: $SESSION_ID
Tool: $TOOL_NAME
Timestamp: $TIMESTAMP
Co-Authored-By: Claude Code <noreply@anthropic.com>"
if git commit -m "$COMMIT_MSG" >> "$LOG_FILE" 2>&1; then
echo "[$TIMESTAMP] Committed $REL_PATH (session $SESSION_ID)" >> "$LOG_FILE"
else
echo "[$TIMESTAMP] Commit failed for $REL_PATH (see lines above)" >> "$LOG_FILE"
exit 0
fi
# Push (best effort — log failures but don't fail the hook). If the push
# fails (offline, conflicts, etc.) the commit is still safely on the local
# branch and the user can push it manually later.
if git push >> "$LOG_FILE" 2>&1; then
echo "[$TIMESTAMP] Pushed $REL_PATH to remote" >> "$LOG_FILE"
else
echo "[$TIMESTAMP] Push failed for $REL_PATH (commit is safe locally)" >> "$LOG_FILE"
fi
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment