"Use when composing a git commit message with shell-active characters — backticks, markdown links, embedded quotes, multi-line code blocks — anything a HEREDOC could re-parse. Invoked by '/commit-msg', or whenever the drafted message contains any of these. Writes the message to a file (.git/COMMIT_EDITMSG, or a scratchpad path in a linked worktree) and commits via 'git commit -F' so the shell never sees the body."
Install
npx skillscat add sirmaelstrom/workit/commit-msg Install via the SkillsCat registry.
commit-msg — File-based git commit
The HEREDOC pattern git commit -m "$(cat <<'EOF' ... EOF)" works for plain text but breaks on shell-active content in the body: backticks trigger command substitution under the outer $(...), embedded quotes can break the wrapping, and escape sequences get re-interpreted. File-based commits via git commit -F <file> bypass shell parsing of the message entirely.
Repository context (injected at load time)
The two read-only git reads below run automatically when this skill loads, so the staged changes and recent history are already in front of you before you compose — no separate status/log round-trip needed. (This uses Claude Code's load-time shell injection. If you would rather not run shell when a skill loads, set disableSkillShellExecution to true in settings.json; the skill still works without it via the fallback note below.)
Staged / working-tree status:
!git status --short 2>/dev/null || echo "(no staged changes, or not a git repo - run git status yourself)"
Recent commits (prefix + style reference):
!git log --oneline -10 2>/dev/null || echo "(no commit history - run git log yourself)"
If either block shows the parenthetical placeholder instead of real output (kill switch on, or not a git repo), just run the command yourself at the step below. The injection is an accelerant, not a dependency.
When to invoke
Invoke when any of these hold:
- The user explicitly says
/commit-msg, "use file-based commit", or similar. - The drafted message contains backticks (e.g.
`parseConfig()`,`--no-verify`, command refs). - The message contains markdown links
[text](url). - The message contains embedded double or single quotes.
- The message contains a code fence or multi-line code block.
- The message is multi-paragraph with formatting you want to land verbatim.
For plain prose with no shell-active characters, an inline message is fine — don't spin up a file for those. (Shell-scoped note: HEREDOC is the Bash tool's inline form; in a PowerShell tool call the equivalent is a single-quoted here-string @'…'@ with the closing '@ at column 0. The -F file path below is identical under both shells — when in doubt, use the file.)
Workflow
1. Verify there is something to commit
The staged status is injected above under "Repository context" — use it. If that block shows the placeholder (or you need a fresh read), run git status --short yourself. If nothing is staged and the user did not explicitly request git commit -a, stop and report. Do not create empty commits.
2. Compose the message
Draft based on the staged diff plus user intent. Conventions:
- Match the repository's prefix style — the recent commits are injected above under "Repository context." If that block is placeholder or absent, run
git log --oneline -10yourself (e.g.feat:,fix:,docs(readme):,chore(ci):). - Subject line under 70 chars.
- Body explains WHY, not WHAT. Skip mechanics the diff already shows.
- Exclude internal review scaffolding from the message body (iteration/wave markers, reviewer names, PR thread IDs, "addressed feedback from…"). Write the message as the squashed, durable record of the change.
- If the repo has an AI-attribution convention (a trailer or marker), follow it; otherwise don't invent one.
3. Write the message file
Default: use the Write tool to put the full message into <repo_root>/.git/COMMIT_EDITMSG. This is git's own scratchpad — it gets overwritten on the next commit anyway, so no cleanup is required.
⚠️ In a linked worktree, .git is a FILE (a gitdir pointer), so any path under .git/ fails with Not a directory (auto-memory worktree-git-is-a-file-commit-msg-path). If git rev-parse --git-dir doesn't return .git, you're in a worktree: write the message to the session scratchpad (or any temp path) instead and pass that absolute path to git commit -F.
If the working directory is not the repo root, run git rev-parse --show-toplevel first to find the right path.
4. Commit
git commit -F .git/COMMIT_EDITMSG(In a worktree: git commit -F <absolute scratchpad path> — see step 3.)
If a pre-commit hook fails, fix the underlying issue and create a NEW commit. Do not amend, do not bypass with --no-verify.
5. Verify
git log -1 --format='%h %s%n%n%b'Confirm the message rendered exactly as drafted. Look for literal backslashes, broken markdown, missing lines, or character substitutions that suggest shell parsing leaked in somewhere.
When NOT to use
- Plain ASCII prose with no shell-active characters — an inline message (Bash HEREDOC / PowerShell single-quoted here-string) is fine, no need to spin up a file.
git tag -m/git notes add— those have their own-Fmechanisms; the principle is the same but the commands differ.- Amending an existing commit at the user's explicit request — use
git commit --amend -F <the same message file>with the same file-write step (worktree caveat from step 3 applies).
Why this exists
Recurring pattern: HEREDOC-based commit messages with backticks, markdown links, and embedded quotes cause git commit --amend cycles when the outer shell re-parses content that should have been literal. File-based commits eliminate the entire class of shell-parsing bugs — write the message to a file, hand git the file, and the shell never touches the body.