Add git-as-state hooks to an agent — auto-commits on Stop, rebases on SessionStart, snapshots on PreCompact. Gives agents durable cross-session memory through their own repo.
Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prüfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fügen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prüfen und installieren.
Ein direkter Befehl überspringt den Prüf-Prompt. Prüfen Sie die Quelle, bevor Sie ihn ausführen.
Add git-as-state hooks to an agent — auto-commits on Stop, rebases on SessionStart, snapshots on PreCompact. Gives agents durable cross-session memory through their own repo.
{"mirror":"abilities@dc855a3 plugins/agent-dev/skills/add-git-sync","version":"1.2","created":"2026-04-21T00:00:00.000Z","author":"Ability.ai","changelog":["1.2: The .gitignore block now carries `.claude/settings.json` + `!.claude/settings.json` (plain rule first, negation last). Trinity#2036 ignores that file fleet-wide and untracks a committed copy on every Push — and it is exactly where this skill registers its hooks, so on a deployed agent the entire setup silently ceased to exist. First-run checklist now stages the hook files explicitly and verifies with git check-ignore","1.1: State why the hooks pair with deployment — Trinity deploys by cloning the repo and tracking the branch, so these hooks keep the branch tip the real agent state and make git_pull//trinity:sync carry work forward","1.0: Initial version — installs git-as-state hooks (Stop auto-commit, SessionStart rebase, PreCompact snapshot) so an agent's own repo becomes durable cross-session memory"]}
category
agent-development
Add Git Sync
ℹ️ First, set expectations: before anything else, print one short line with this skill's version and its most recent change — the top entry of metadata.changelog above — e.g. add-git-sync vX.Y — recent: <summary>. Then proceed.
Installs three hooks that turn the agent's own repo into its durable state layer:
Hook
Event
Job
git-session-start.sh
SessionStart (startup/resume)
Stash local drift → fetch → rebase onto remote branch → restore drift. Every session begins consistent with origin.
git-pre-compact.sh
PreCompact
Fast local commit so in-flight work survives context compaction. No push.
git-sync.sh
Stop (async)
git add -A → commit → push with rebase-on-reject retry. Drops .git/SYNC_FAILED if it can't reconcile; SessionStart surfaces the note next run.
Who this is for: any agent whose directory is its own git repo and benefits from cross-session continuity (Trinity-deployed agents, autonomous workers, long-running research agents). Not appropriate for agents nested inside a larger monorepo.
Why this pairs with deployment: Trinity deploys an agent by cloning its GitHub repo and tracking the branch, so the remote agent's state is whatever the branch tip holds. These hooks keep that tip honest — work is committed and pushed at session end instead of lingering uncommitted, so a git_pull (or /trinity:sync) actually carries the agent forward. Install this before deploying, not after the first surprise.
Escape hatches baked in:
touch .git/NO_AUTOSYNC — disables all three hooks
echo "..." > .git/SELF_SELECT_MSG — subagents write structured commit messages that survive the generic Stop-hook commit
.git/SYNC_FAILED — propagates push failures forward to the next session
Process
Step 1: Preflight
Run from inside the target agent directory (or ask the user for the path). Verify:
# 2. CWD is a git repo root (not nested). Resolve both sides with `pwd -P`
# so macOS /tmp vs /private/tmp symlinks don't cause a false positive.
if
"$REPO_ROOT"
then
# Not a git repo — ask whether to init
elif
"$(cd "$REPO_ROOT" && pwd -P)"
"$(pwd -P)"
then
# Nested inside a larger repo — refuse or offer local-only
echo
"ERROR: Agent dir is nested inside $REPO_ROOT. Hooks would push the wrong scope."
exit
fi
# 3. Does .claude/settings.json exist? (create if missing)
# 4. Do any of the target hooks already exist? (if yes, diff and ask)
If no .git/: ask "Initialize git repo here?" If yes, git init.
Step 2: Ask configuration questions
Use AskUserQuestion with these three questions (in order):
Q1 — Sync mode
remote-push (default) — Stop hook commits and pushes
local-only — Stop hook commits but does not push (use when there's no remote or the agent is air-gapped)
Q2 — Remote and branch (only if Q1 = remote-push)
Default: origin / main
If git remote get-url origin fails, ask for the remote URL and set it up before continuing
Q3 — Commit co-author line
Default: Co-Authored-By: Claude <noreply@anthropic.com>
Or blank (no co-author)
Step 3: Install hook scripts
Templates live in this skill's templates/ directory. Copy to the agent's .claude/hooks/ with placeholder substitution:
SKILL_DIR="$(dirname "$0")"# resolved at runtime
TEMPLATES="$SKILL_DIR/templates"
HOOKS_DIR="$AGENT_DIR/.claude/hooks"mkdir -p "$HOOKS_DIR"substitute() {
local src="$1" dst="$2"
sed -e "s|__BRANCH__|$BRANCH|g" \
-e "s|__REMOTE__|$REMOTE|g" \
-e "s|__COAUTHOR__|$COAUTHOR|g" \
"$src" > "$dst"chmod +x "$dst"
}
if [ "$MODE" = "remote-push" ]; then
substitute "$TEMPLATES/git-session-start.sh""$HOOKS_DIR/git-session-start.sh"
substitute "$TEMPLATES/git-pre-compact.sh""$HOOKS_DIR/git-pre-compact.sh"
substitute "$TEMPLATES/git-sync.sh""$HOOKS_DIR/git-sync.sh"else# local-only: no SessionStart (no remote to rebase from)
substitute "$TEMPLATES/git-pre-compact.sh""$HOOKS_DIR/git-pre-compact.sh"
substitute "$TEMPLATES/git-sync-local.sh""$HOOKS_DIR/git-sync.sh"fi
Idempotency: before overwriting any existing hook file, diff content. If identical, skip silently. If different, show the diff and ask the user whether to overwrite.
Step 4: Merge into .claude/settings.json
Load existing settings (create {} if missing), merge the hooks block, write back. Use jq to keep the JSON canonical:
SETTINGS="$AGENT_DIR/.claude/settings.json"
[ -f "$SETTINGS" ] || echo'{}' > "$SETTINGS"# Build the hooks JSON we want to ensure is present
NEW_HOOKS=$(jq -n --arg mode "$MODE"'{
SessionStart: (if $mode == "remote-push" then [{
matcher: "startup|resume",
hooks: [{
type: "command",
command: "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/git-session-start.sh",
timeout: 20,
statusMessage: "Syncing from git..."
}]
}] else null end),
PreCompact: [{
hooks: [{
type: "command",
command: "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/git-pre-compact.sh",
timeout: 15,
statusMessage: "Snapshotting pre-compact..."
}]
}],
Stop: [{
hooks: [{
type: "command",
command: "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/git-sync.sh",
timeout: 45,
statusMessage: "Syncing to git...",
async: true
}]
}]
} | with_entries(select(.value != null))')
# Merge: existing + new (new entries fill gaps; do NOT overwrite existing hook arrays# for the same event — append instead, and warn the user if there are duplicates).
jq --argjson new "$NEW_HOOKS"'
.hooks = (
(.hooks // {}) as $existing
| $new as $toAdd
| reduce ($toAdd | keys_unsorted[]) as $event ($existing;
.[$event] = (( .[$event] // [] ) + $toAdd[$event])
)
)
'"$SETTINGS" > "$SETTINGS.tmp" && mv"$SETTINGS.tmp""$SETTINGS"
Idempotency check: before merging, scan existing hooks.<event> arrays for any entry whose command already points at .claude/hooks/git-*.sh. If found, skip that event and print [already installed].
Step 5: Append runtime-state exclusions to .gitignore
Add only entries not already present. Block-wrap with a header so it's clear what added them:
# --- agent-dev:add-git-sync runtime exclusions ---
# Trinity (#2036) ignores .claude/settings.json fleet-wide and `git rm --cached`s
# an already-committed copy on the next Push, because the base image bakes its own
# container-only copy and HOME is the repo root. THIS SKILL registers its hooks in
# that file — so on a Trinity-deployed agent the registration would be untracked and
# the whole setup would silently stop existing. Emit BOTH lines, plain rule FIRST:
# the plain line satisfies Trinity's exact-line `grep -qxF` gate so it stops appending
# its own copy at the end of the file, and the negation comes last so git's
# last-match-wins re-includes the file.
.claude/settings.json
!.claude/settings.json
.claude/projects/
.claude/statsig/
.claude/todos/
.claude/debug/
.claude/sessions/
.claude/shell-snapshots/
.claude/telemetry/
.claude/cache/
.claude/backups/
.claude.json
.mcp.json
.env
*.key
*.pem
credentials.json
# --- end add-git-sync ---
Before appending, grep each line against the existing .gitignore and skip duplicates. If the entire block header is already present, skip the section entirely.
Step 6: Add a section to the agent's CLAUDE.md
Check if a ## Git Sync or **[GIT SYNC (AUTOMATIC)]** section already exists. If yes, skip. If no, append:
---
## Git Sync (Automatic)
Three hooks manage git sync for this agent. Defined in `.claude/settings.json`, scripts in `.claude/hooks/`.
| Hook | Script | Purpose |
|---|---|---|
| `SessionStart` | `git-session-start.sh` | Auto-stash drift, fetch + rebase onto `<REMOTE>/<BRANCH>`, restore drift. |
| `PreCompact` | `git-pre-compact.sh` | Snapshot commit before compaction so mid-flight work survives context loss. No push. |
| `Stop` (async) | `git-sync.sh` | Commit staged files, push with rebase-on-reject retry (max 2). Honors `.git/SELF_SELECT_MSG` for structured commit messages. |
**Escape hatches:**-`touch .git/NO_AUTOSYNC` — disable all three hooks until removed
-`echo "msg" > .git/SELF_SELECT_MSG` — override Stop-hook commit message (consumed after use)
-`.git/SYNC_FAILED` — written when push can't reconcile; surfaced in next SessionStart
**Design principle:** hooks enforce session-boundary consistency. Inside a session the agent is free; at boundaries (start / compact / stop) the repo reconciles.
Substitute <REMOTE> and <BRANCH> (or drop the SessionStart row entirely in local-only mode).
Step 7: Smoke test
Before declaring success, run:
# Syntax check each hookfor h in"$HOOKS_DIR"/git-*.sh; do
bash -n "$h" || { echo"SYNTAX ERROR in $h"; exit 1; }
done# settings.json parses
jq empty "$SETTINGS" || { echo"settings.json is invalid JSON"; exit 1; }
# .gitignore parses (just ensure it's readable)
[ -r "$AGENT_DIR/.gitignore" ]
Print a summary of what was added and what was skipped (idempotent no-ops).
Step 8: Print next steps
## Git Sync Installed
Mode: **<MODE>** (remote-push / local-only)
Remote: `<REMOTE>/<BRANCH>` (if applicable)
### What's active- SessionStart: auto-rebase onto remote
- PreCompact: safety snapshot
- Stop: auto-commit (and push, if remote-push)
### First-run checklist1. Commit this setup: `git add .claude/hooks/ .claude/settings.json .gitignore CLAUDE.md && git commit -m "Add git-sync hooks"` — then verify the negation actually wins with `git check-ignore -v .claude/settings.json` (it should report the `!` line, or report nothing). If `settings.json` is ignored, the hooks are registered nowhere on any clone but this one.
2. If remote-push: confirm `git remote -v` points at the right repo
3. Trigger a test: make a trivial edit, let the session end, verify a Heartbeat commit appears
### Disable temporarily`touch .git/NO_AUTOSYNC` — re-enable by deleting the file.
Notes
Prerequisite:jq must be installed (it's used by every hook and by this skill's settings merge). Check with command -v jq in Step 1; bail with install instructions if missing.
Monorepo guard: the preflight refuses to install when the agent dir is nested inside a larger repo. The user can either pull the agent into its own repo or pick local-only mode at their own risk (the Stop hook would still commit to the wrong scope — safer to refuse).
Co-author line: the __COAUTHOR__ placeholder is substituted as a single line. If the user wants multi-line sign-offs, they can edit git-sync.sh directly post-install.
Re-running is safe. Every write checks for prior state. Running the skill twice produces no diff on the second run.