| name | commit |
| description | Stages and commits the current changes onto a safe working branch, enforcing branch discipline and optionally pushing upstream. TRIGGER when: the user wants to commit, save, or check in the current changes. DO NOT TRIGGER when: opening a pull request → pr. |
| argument-hint | Optional `push` to push the branch upstream after committing. |
| compatibility | Requires a Git working tree. |
| metadata | {"version":"0.1.0","author":"OpsMill"} |
Commit Changes
Introduction
Stage and commit the current changes onto a safe working branch. This skill enforces branch discipline: when the current branch is unsafe to commit to (the canonical rules live in step 2 below), it proposes a new fix/, feat/, docs/, etc. branch and switches to it after approval. It works equally well for brand-new work and for additional commits on an existing feature branch. If the user passes push, the branch is pushed upstream after the commit.
Arguments
$ARGUMENTS
Supported arguments:
push — After committing, push the branch upstream (git push -u origin <branch> on first push, otherwise git push).
Main Tasks
1. Assess Current State
- Run
git status to see staged, unstaged, and untracked files.
- Run
git branch --show-current to identify the current branch.
- Run
git diff --stat and git diff --cached --stat to summarise the change footprint.
- If there are no changes to commit (working tree clean, nothing staged) — STOP and tell the user there's nothing to commit. Do not create an empty commit.
- If a merge, rebase, or cherry-pick is in progress (
git status reports it, or .git/MERGE_HEAD / .git/rebase-* / .git/CHERRY_PICK_HEAD exist) — STOP and surface it to the user rather than committing into the middle of that operation.
2. Branch Safety — Never Commit to a Protected or Placeholder Branch
A branch is unsafe to commit to if any of the following hold:
- It is the repository's default branch (resolve with
git symbolic-ref refs/remotes/origin/HEAD --short 2>/dev/null | sed 's@^origin/@@', falling back to git remote show origin | sed -n 's/ *HEAD branch: //p').
- It is a long-standing integration branch by name:
main, master, stable, develop, dev, trunk.
- It is a release branch: matches
release/*, release-*, or is otherwise clearly named release-something (e.g. release-2026.05, releases/v3).
- It is a placeholder / scratch branch generated by the harness or a worktree — specifically the auto-generated
adjective-animal pattern (e.g. wary-cuckoo, lumbar-gorilla, wacky-otter, silent-fox), where the second word is an animal. These exist only to host a session and should not be committed to directly. Be careful not to over-match: legitimate two-word branches like dark-mode, rate-limit, cache-layer, or user-auth are real feature branches, not scratch branches. When a name is ambiguous (two hyphenated words but not clearly adjective-animal), do not assert it is unsafe — instead ask the user "this looks like it might be a generated scratch branch — is it, or is it a real branch you want to commit to?" and proceed on their answer.
If the current branch is unsafe by any of those rules:
- Do NOT commit on the current branch.
- Analyse the changes (diffs from step 1) to understand whether the work is a bug fix or new/extended behaviour.
- Propose a branch name using the conventional prefix that matches the change:
- Bug fix →
fix/<short-description> (e.g. fix/graphql-codegen-missing-types).
- New feature or enhancement →
feat/<short-description> (e.g. feat/add-commit-skill).
- Docs-only, chore, refactor, etc. → mirror the conventional-commit type:
docs/<…>, chore/<…>, refactor/<…>.
- Use a concise kebab-case slug. If the repo has a visible naming convention in
git branch -a or in AGENTS.md / CONTRIBUTING.md, follow that instead.
- Present the proposed branch name to the user and wait for explicit approval (the user may suggest a different name).
- After approval, create and switch to the branch:
git checkout -b <branch-name>. The uncommitted changes carry across automatically.
If the current branch is already a real feature branch (i.e. not in any of the unsafe categories above), continue on it. Do not create a new branch unless the user explicitly asks for one.
3. Stage Changes
- Review what will be staged. Prefer adding files explicitly by path rather than
git add -A or git add ., especially when untracked files are present — this avoids accidentally committing secrets (.env, credentials, key files) or large/generated artefacts.
- If staged changes already exist (the user pre-staged), respect that staging and only add additional files when it's clearly intended.
- Warn the user and require explicit confirmation before staging any file that looks sensitive (
.env*, *secret*, *credential*, *.key, *.pem) or generated/large (node_modules/, build artefacts, dist/, __pycache__/, lockfiles changed unexpectedly).
4. Draft the Commit Message
Follow the repo's conventional-commit style (visible in git log):
- Use a type prefix:
feat:, fix:, refactor:, docs:, test:, chore:, ci:, etc.
- Optional scope in parentheses:
feat(backend): ..., fix(e2e): ....
- Subject line under ~72 characters, imperative mood, no trailing period.
- Focus on the why — what changed in terms of behaviour or capability, not a file list.
- For multi-area changes, add a short body explaining the motivation.
- Respect any repo or harness commit-trailer convention (e.g. a
Co-Authored-By trailer the harness expects to append). Don't strip trailers that are already part of the project's workflow.
- Never write a session-link trailer into the commit — strip it if the harness added one. Some harnesses append a line pointing at the private agent session, e.g.
Claude-Session: https://claude.ai/code/session_… (or any trailer carrying a URL to an agent, chat, or coding session). These leak an internal, usually unshareable URL into the project's permanent — often public — git history, and they reference session state no future reader can open, so they're pure noise at best and a disclosure at worst. This is the one exception to "don't strip trailers" above: if the harness has inserted such a line, remove it before committing, and never author one yourself. Legitimate project trailers (Co-Authored-By, Signed-off-by, Reviewed-by, etc.) still stay.
Inspect the most recent ~20 commits with git log --oneline -20 and mirror the style you see (scope conventions, capitalisation, whether bodies are used). Generic examples:
fix: correct off-by-one in pagination cursor
docs: archive completed specs and extract durable knowledge
feat: add commit skill for safe branch discipline
Present the proposed commit message to the user before committing. Adjust based on feedback.
5. Create the Commit
-
Run git commit -m "<message>". For multi-line messages, use a HEREDOC:
git commit -m "$(cat <<'EOF'
<subject>
<body>
EOF
)"
-
Do NOT pass --no-verify — let pre-commit hooks run. If a hook fails:
- Read the hook output carefully.
- Fix the underlying issue (formatting, lint, etc.) rather than bypassing.
- Re-stage the fixed files and create a NEW commit so each fix stays traceable. (Don't reach for
--amend to absorb hook fixes — only amend a commit you deliberately intend to rewrite.)
-
Run git status after the commit to confirm a clean tree and verify success.
-
Run git log -1 --stat so the user can see what landed.
6. Push Upstream (only when push argument is provided)
Skip this phase entirely if push was NOT passed.
When push IS provided:
- Re-verify the branch is not unsafe per the rules in step 2 (defence in depth, though step 2 should have prevented this).
- Check whether the branch already tracks a remote:
git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null
- If no upstream exists:
git push -u origin <branch-name>.
- If an upstream exists:
git push.
- Never use
--force or --force-with-lease unless the user explicitly asks for it. Regular git push will fail if the remote has diverged — surface that error to the user instead of overwriting.
- Report the push result and, if a remote URL is configured (
git config --get remote.origin.url), the branch URL the user can open.
Notes
Branch Safety:
- The canonical list of unsafe branches lives in step 2 — that is the single source of truth; don't re-derive it from memory.
- If asked to override the rule, refuse and explain — the user can switch branches themselves first if they truly intend to commit there (in which case this skill no longer applies).
This is a discipline skill — hold the line under pressure. Refusing to commit to a protected branch is the whole point, and the excuses below will appear. None of them change where the commit should land:
| Excuse / pressure | Reality |
|---|
| "It's urgent / it's an emergency, just commit to main." | Urgency doesn't change where the commit lands. A feat//fix/ branch takes seconds and is just as fast to merge. |
| "Just this once, skip the branch." | There is no "just once" — the rule exists precisely for the tempting one-off. Propose a branch. |
| "It's a tiny change, the branch is overkill." | Size is irrelevant to branch safety; a one-line fix on main is still a direct commit to a protected branch. |
"The hook is failing, just use --no-verify." | Fix the violation instead. Bypassing the hook defeats its purpose. |
"The remote diverged, just --force." | Never force-push unless the user explicitly asks. Surface the divergence instead. |
Red-flags self-check — if you catch yourself thinking any of these, STOP and re-read step 2:
- "This branch is probably fine to commit to."
- "I'll commit here and sort the branch out later."
- "The user clearly wants this on
main, so the rule doesn't apply."
Secret Hygiene:
- Prefer explicit
git add <path> over wholesale git add -A.
- Warn before staging anything that looks like a secret or credential.
- A session-link trailer (
Claude-Session: / any agent-session URL) is a disclosure too — strip it from the commit message before committing, per step 4.
Hook Discipline:
- Pre-commit hooks exist for good reasons. Fix violations rather than bypassing with
--no-verify.
Idempotency:
- Safe to invoke repeatedly. With no changes to commit, the skill exits cleanly without creating an empty commit.
Expected Outcome
- All staged/unstaged changes that the user wanted to capture are committed on a safe working branch.
- The branch is named meaningfully (existing real feature branch preserved, or a new
fix/<…> / feat/<…> / docs/<…> / etc. name approved by the user).
- The commit message follows the repo's existing conventional-commit style.
- If
push was provided, the branch is pushed upstream.
- Every branch deemed unsafe in step 2 is left untouched in all cases.