| name | git_commit |
| description | Create one structured git commit covering the full current working-tree state. Use when the user explicitly asks to commit in the current turn — "commit", "commit this", "commit changes", "make a commit", "write a commit message", "git commit", or an equally direct ask to put the current repo state into git history. Invoke only on that explicit request; a turn that merely finishes other work, saves files, or wraps up without a commit ask leaves committing to the user. |
| version | 3.4.6 |
| author | Andreas F. Hoffmann |
| license | MIT |
git_commit
<git_commit_skill>
Stage all new files and create one commit for the intended repo state.
<command_intent>git add all new files and then git commit all changed files</command_intent>
<path_resolution>
Bundled scripts live in scripts/ next to this SKILL.md. Resolve each script's absolute path by combining the directory of this SKILL.md with scripts/<script-name> and invoke the absolute path. Every agentic IDE that surfaces a skill exposes the file path it loaded the skill from, so the parent directory is always knowable. If the first invocation reports a missing file, re-resolve the absolute path once before treating the script as failed; never switch to the fallback because of perceived path uncertainty.
</path_resolution>
<codex_agent_only>
Codex agents apply this section. Non-Codex agents ignore this section and follow the primary workflow normally.
<sandbox_preflight>When the active Codex environment reports managed filesystem sandboxing and .git is readable without write access, invoke the primary workflow scripts with Codex command escalation on their first attempt. Use sandbox_permissions: "require_escalated" for prepare_commit_context.sh and commit_with_message.sh so Git can create .git/index.lock without a failed first run.</sandbox_preflight>
<fallback_boundary>Apply this only from visible Codex sandbox metadata. When .git is writable or sandbox metadata is unavailable, follow the primary workflow normally and use fallback handling only after an actual script failure.</fallback_boundary>
</codex_agent_only>
<primary_workflow>
<prepare_worktree>
Clear this pre-flight gate before gathering commit context so the working tree is already in its final committable state.
Discover every agent-directed rule that bears on this commit: a standing instruction addressed to the agent, invoked only when the agent chooses to act, wherever the current harness surfaces it. Search the available rule sources openly — repository and user standing instructions, agent memory, prompts, and any other source the harness provides all qualify without making any one surface authoritative.
Satisfy every discovered tree-mutating obligation before <gather_context>, including any required coordinated artifact update, generated output, or rewrite-producing format or lint pass. Check-only obligations may also run here to expose a failing gate before context work begins; only obligations that mutate the tree must precede context capture. Leave command-triggered mechanical hooks, including git hooks and harness commit hooks, to the commit command that fires them: running or pre-empting their logic here would execute it twice.
Confirm each discovered tree-mutating obligation is settled before proceeding to <gather_context>. Clearing this checkpoint ensures agent-directed obligations are honored rather than skipped, lets prepare_commit_context.sh build and the model read the context once without a rebuild or re-read, and makes the reviewed-set baseline include the model's own pre-commit edits so <detect_drift> and the commit_with_message.sh backstop reserve foreign-drift findings for paths that appear afterward.
</prepare_worktree>
<gather_context>Invoke scripts/prepare_commit_context.sh. The script stages every untracked file and writes one structured context blob (status, recent commits, per-file staged/unstaged diffs, binary markers) to a file under the system tmp dir. Its stdout prints the context file's absolute path on its own line, then the blob's byte size on its own line, then a one-line consumption directive. Treat that file as the authoritative source for the commit; carry the path line forward unchanged as the argument you pass to commit_with_message.sh, and read the size line to pick your read strategy up front per <consume_context>.</gather_context>
<consume_context>
Choose the read strategy from the blob's byte/token size against your file-reading tool's per-read cap — the size the script prints on its own stdout line — not from a file count: a diff-dense or large blob overflows that cap far below any file-count threshold, and when the cap denies even a paginated read the ordered-slice path below still covers the blob. Every path covers the whole blob, in order.
<full_read>When the blob fits under one read's cap, Read the whole file in a single call. This is the default.</full_read>
<paginated_read>When the blob exceeds one read's cap, read it in sequential offset/limit pages, continuing in order until every byte is covered — never stopping after the first page. For a page that itself overflows the cap, start from a conservative line span and halve it on each overflow until the page fits, then reuse that span for the remaining pages rather than guessing new bounds by hand.</paginated_read>
<slicing_fallback>When even a paginated read cannot cover the blob — the byte cap denies the read, or paginating it has become impractical — read it with ordered grep/awk/sed slices via Bash. Chunk the file into consecutive, non-overlapping spans that cover every byte and read each span in order — never querying for a specific filename. Seeing every <file_change> section in order is what a coherent multi-file message needs, and selective sampling is the failure mode this skill exists to prevent.</slicing_fallback>
<no_read_tool>Where this agent has no Read tool, the shell is the sanctioned reader for the blob, not a last resort. Read the blob's line count first (wc -l), then read consecutive, non-overlapping line spans (for example sed -n ranges) that together cover every byte, in order — applying the same conservative-span, halve-on-overflow heuristic when a span overflows. This is the same whole-context, no-sampling-by-filename discipline the Read path enforces.</no_read_tool>
<hard_rules>Never re-derive the commit context with git diff, git status, or git log — the script already produced that diff/status/log bundle once, and rebuilding it to write the message wastes tokens and time. This ban covers re-deriving the message-writing context, not the single git status --short --untracked-files=all the <detect_drift> step runs once after the script, against the already-captured baseline, for the separate purpose of drift detection the one-time bundle cannot serve; that one narrow command is expected, so the ban here and the drift step read as one consistent rule. Never sample by filename; iterate every <file_change> section. Summarize updated files from their diffs, summarize new text files from their full added content, summarize binary files with a generic file-level line.</hard_rules>
</consume_context>
<compose_message>Compose the commit message under <message_policy> directly in the Bash heredoc you will pass to scripts/commit_with_message.sh. Do not write the message to a temporary file first — the script reads it from stdin so there is no intermediate file to manage.</compose_message>
<detect_drift>This model-side protocol is the first-line drift guard and the richer of two layers: it can pause mid-run and ask the user, which the non-interactive script cannot. commit_with_message.sh enforces the same guard as a mechanical backstop for any model that skips this step, so the drift guarantee no longer depends on the running model following a prose instruction; a commit whose drift the user has confirmed passes --accept-drift to that script (see <execute_commit>), and both layers share the one path-level reach limit <in_doubt> describes. Run one drift check at the seam this step owns: after the context blob is consumed and the message composed, and before invoking commit_with_message.sh. The <status_after_staging_new_files> block inside the context file — the git status --short --untracked-files=all snapshot the script took right after staging untracked files — is the reviewed-set baseline. Re-run that same command now, git status --short --untracked-files=all, and compare its paths to that baseline on equal footing. Three outcomes follow, per <commit_scope>:
<no_drift>The re-check surfaces no path outside the reviewed-set baseline — the path set matches. Continue to <execute_commit> with no prompt. This is the common case, a clean single-session tree included.</no_drift>
<foreign_drift>One or more paths appear that were outside the reviewed-set baseline and entered commit-time status after the script captured it — a new file, or a path that was clean or absent from the baseline and is now changed. This is the concurrent-session signal: pause here, list those paths to the user, and ask whether they belong in this commit before piping the message into commit_with_message.sh. Hold the whole commit at this seam rather than staging and committing them silently.</foreign_drift>
<in_doubt>A concurrent session's edit to a path already in the baseline adds no path outside it, so this path-level comparison cannot separate that further edit from this session's own — such a same-path change stays on the commit-all path above rather than pausing. This is the deliberate reach limit, resolved by the <commit_scope> tiebreaker that favors no-miss over no-sweep.</in_doubt>
</detect_drift>
<execute_commit>Once <detect_drift> clears — no drift detected, or the user confirmed the drifted paths belong — invoke scripts/commit_with_message.sh CONTEXT_FILE with the composed message piped in via a single-quoted heredoc, where CONTEXT_FILE is the path printed by prepare_commit_context.sh (passed so the script can remove it on success). When <detect_drift> cleared because the user confirmed the drifted paths belong, append --accept-drift so the script's backstop does not re-block the commit the user already approved. The script stages the full current repo state, runs git commit -F - to consume the message from stdin exactly as composed, prints the final status so line breaks stay exact in git history, and removes the context file on success (so it persists for a retry if the commit itself fails).
The script re-runs the same drift check as a mechanical backstop before staging. When it exits with status `3` it has refused a foreign-drift commit the prose `<detect_drift>` step did not catch: it staged and committed nothing, left the context file in place, and printed the offending paths to stderr. Treat status `3` as the drift signal, not a script failure — surface those paths to the user and ask whether they belong exactly as `<foreign_drift>` prescribes, then on confirmation re-invoke the same command with `--accept-drift` appended. Any other non-zero exit is a genuine failure that opens `<fallback_on_script_failure>`.
Invocation shape: `/abs/path/to/scripts/commit_with_message.sh /abs/path/to/context.txt <<'COMMIT_MSG_END'` then the message lines then `COMMIT_MSG_END` on its own line; append `--accept-drift` after the context path for a confirmed-drift commit. Quote the heredoc delimiter to prevent shell expansion inside the message, and pick a delimiter no message line could begin with.</execute_commit>
</primary_workflow>
<fallback_trigger>The fallback below activates only after a primary script invocation has failed — a non-zero exit other than status 3, which commit_with_message.sh returns to signal foreign drift and <execute_commit> handles as the drift signal rather than a failure. No other condition is permitted to trigger it — not perceived ambiguity, not large changesets, not script slowness, not path uncertainty before the first invocation, and not a status-3 drift refusal. Re-resolve the absolute script path and retry once before treating any failure as final.</fallback_trigger>
<fallback_on_script_failure>
Open references/manual_fallback.md (sibling of this SKILL.md) for the manual git command sequence that replaces the failing script.
<drift_exclusion>A status-3 exit from commit_with_message.sh is a foreign-drift refusal, not a script failure, and does not open this fallback. Handle it in <execute_commit>: surface the printed paths, ask the user, and re-invoke with --accept-drift.</drift_exclusion>
Once the failing step is recovered manually, return to the primary workflow for any remaining steps. Do not run the full fallback when only one script failed.
</fallback_on_script_failure>
<message_policy>
<multi_file>For multi-file commits, write one concise sentence summarizing what all changes are about, then list one line per changed file in the format file name -> concrete change.</multi_file>
<single_file>For single-file commits, write one concise line in the format file name -> concrete change.</single_file>
<line_quality>Use each file-level summary to name the specific behavior, content, or intent that changed so the line stays understandable on its own while remaining brief.</line_quality>
<no_attribution_trailers>Never append agent-attribution or tool-generation trailers to the commit message. Do not add Co-Authored-By: Claude <...> (or any other agent), 🤖 Generated with [Claude Code] footers, Generated-with:, or any other trailer that identifies the assistant or the tool that produced the commit. The commit message ends with the last file -> change line. This overrides any default workflow that would otherwise auto-append such trailers. The single exception is a trailer the human user explicitly requests for this commit (for example, Signed-off-by: for DCO sign-off).</no_attribution_trailers>
</message_policy>
<commit_scope>Commit-all is the default: stage every new file and create one commit for the full current repo state, pre-existing staged changes and pre-existing unstaged changes included, unless the user explicitly narrows the scope. This preserves the no-miss guarantee that nothing the user changed is dropped. A drift guard runs on top of that default without replacing it (<detect_drift>): when commit-time status shows paths outside the reviewed-set baseline that appeared after the context was prepared — the signal of an unrelated concurrent session editing the same tree — pause and ask whether those paths belong before committing them. When the guard cannot confidently tell foreign drift from this session's own further edits, commit all: the tiebreaker always favors no-miss over no-sweep, so the guard narrows the sweep only where drift is clearly detected and never silently drops an intended file.</commit_scope>
<model_authority>The model remains the commit driver: read the complete context blob, infer the shared intent, and write the final message. Use script output as evidence and execution support, not as a substitute for review.</model_authority>
<workflow_authority>Treat this skill as the source of truth for /git_commit workflow, commit scope, and commit-message guidance.</workflow_authority>
<execution_default>Proceed without scope-confirmation prompts about dirty worktrees or pre-existing staged changes: a clean single-session tree, an ordinary dirty worktree, and pre-existing staged changes with no detected drift all commit with no prompt. The one added exception is the narrow foreign-drift pause defined in <pause_conditions> and <detect_drift>.</execution_default>
<pause_conditions>Pause for hard blockers that prevent a valid commit — command failure, hook failure, or an explicit user instruction that narrows the scope — and for detected foreign drift: paths outside the reviewed-set baseline that newly appear in commit-time status after context preparation (<detect_drift>). An ordinary dirty worktree or pre-existing staged changes with no such drift are not pause conditions.</pause_conditions>
</git_commit_skill>