T-UEU9-document-commit-message-pattern
Status: closed/done · Impact: low · Complexity: small
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to ready before picking up.
SDLC skills that build a commit message from model output keep hitting
the same shell pitfalls: zsh glob-expanding docs(tasks): inside a
heredoc; collisions on /tmp/commit-msg.txt; cache-stale PC: bash
annotations. Documenting a single canonical “build commit message via
mktemp and git commit -F” pattern (and putting it in the skills
that need it) removes the recurring workaround.
From T-DHGL-build-import-planning-skill’s post-mortem:
“One Bash invocation of
git commit -m \"$(cat <<EOF...EOF)\"failed because zsh expandeddocs(tasks):as a glob inside the command-substitution. Worked around by writing the message to a temp file and passing-F. Worth documenting as a pattern in any SDLC skill that constructs commit messages from the model.”
And the same family of issue appears in T-3A6G-implement-entities-migrate:
“
/tmp/commit-msg.txtcollision — orchestrator used this generic temp path for agit commit -Fand a stale file from a previous session was present, so the wrong commit message landed on the start commit (had to--amend). Fix: never use predictable/tmp/<generic>.txtpaths; usemktempor a unique PID/UUID-prefixed name.”
So today: skills construct commit messages inline with heredoc-in- command-substitution and sometimes via predictable temp paths. Both modes have failed once each. Multiple skills repeat the unsafe pattern.
Proposed
Section titled “Proposed”Add a canonical “Committing model-generated messages” snippet to the SDLC skills that build commit messages from LLM output. The snippet:
msgfile="$(mktemp -t sdlc-commit-XXXXXX)"trap 'rm -f "$msgfile"' EXITcat > "$msgfile" <<'EOF'<commit message body — note quoted heredoc delimiter to suppressshell expansion>EOFgit commit -F "$msgfile"Note both safety affordances: mktemp (no /tmp collisions) and a
quoted heredoc delimiter <<'EOF' (no $( ), no glob, no
parameter expansion).
Approach
Section titled “Approach”- Draft the snippet (above), with a one-paragraph rationale that names both pitfalls (collision + glob expansion).
- Inline the snippet under a
ConventionsorNotessubsection of:task-work,task-define,entities-migrate,import-planning,milestones-from-file,project-cleanup,backlog-triage,review-todos. Any skill that builds commit messages from model output. - Replace any existing inline heredoc-in-
-mexamples with the canonical snippet.
Files to touch
Section titled “Files to touch”plugin/skills/task-work/SKILL.mdplugin/skills/task-define/SKILL.mdplugin/skills/entities-migrate/SKILL.mdplugin/skills/import-planning/SKILL.mdplugin/skills/milestones-from-file/SKILL.mdplugin/skills/project-cleanup/SKILL.mdplugin/skills/backlog-triage/SKILL.mdplugin/skills/review-todos/SKILL.md
(One-line edits each, inserting the same snippet.)
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: The canonical
mktemp+ quoted-heredoc +git commit -Fsnippet lives in exactly one place —plugin/conventions/commit-messages.md. Every SKILL.md that constructs commit messages from model output carries a one-line reference to that doc; no SKILL.md duplicates the snippet inline. (Revised from the original “every SKILL.md inlines the snippet” wording per PR #21 review feedback — shared reference is the right shape.) - AC-2: No SDLC SKILL.md still contains a
git commit -m "$(cat <<EOF...EOF)"example. - AC-3: No SDLC SKILL.md still contains a predictable
/tmp/commit-msg.txt(or similar) example; all temp paths in the canonical doc come frommktemp. - AC-4:
/project-checkstill passes.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of T-DHGL-build-import-planning-skill on 2026-05-19. Related friction was also observed in T-3A6G-implement-entities-migrate’s post-mortem.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-19. PR: #21.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
grepconfirms the canonical snippet lives inplugin/conventions/commit-messages.mdonly; each affected SKILL.md carries a single one-line reference. - AC-2: auto —
/usr/bin/grep -rn 'git commit -m "\$(cat'acrossplugin/skills/returns zero hits. - AC-3: auto —
/usr/bin/grep -rn '/tmp/commit-msg'acrossplugin/skills/returns zero hits (predictable paths only appear in the canonical doc’s antipattern section, labeled as such). - AC-4: auto —
/project-check(check_entities.py+ the prose linter from PR #19) passes.
What worked
Section titled “What worked”- Replacing 8 verbatim 13-line blocks with 8 one-line references shrank the diff from +120 lines to +60 lines (the 60 being mostly the new canonical doc itself).
- Convention precedent already existed:
plugin/entities/task/implementation-ready.mdis referenced the same way from multiple skills. Pattern transferred cleanly.
Friction and automation gaps
Section titled “Friction and automation gaps”- Original spec AC-1 mandated inline duplication (“every SKILL.md contains the canonical snippet”). Spec needed revision before the refactor was AC-compliant. Automation gap: the new prose-linter from PR #19 should grow an invariant that flags “verbatim multi-line block appearing in N>1 SKILL.md files” so duplication is caught at PR time before review. Filed implicitly as a follow-up.
- The original draft was produced by the spawn-from-post-mortem sub-agent from a one-line friction bullet. The sub-agent went literal — “document the pattern in each skill that needs it” → “inline the pattern in each skill.” A spawn-from-post-mortem pre-flight question (“should this be a shared doc or per-skill?”) would catch the shape mismatch before the task is materialized.
Out of scope
Section titled “Out of scope”- none