T-7UIC-investigate-sandbox-multiline-commit-denial
Status: closed/done · Impact: low · Complexity: small
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to open/ready before picking up.
The canonical commit-message pattern in
plugin/conventions/commit-messages.md is
tmpf=$(mktemp) && cat > "$tmpf" <<'EOF' ... EOF && git commit -F "$tmpf". During the T-6D5U-add-astro-docs-site implementation
run, several invocations of that pattern were denied by the sandbox
(“Permission to use Bash has been denied”), forcing the implementer to
fall back to single-line git commit -m "..." messages and losing the
multi-paragraph “why” context that conventional-commit bodies are
supposed to capture.
Either: the convention needs a permission-allowlist note so consumers
know what to add up front, or the harness needs a permissive entry
that covers the heredoc shape, or the convention should be rewritten
to use a Write-tool-friendly shape (e.g. write /tmp/cm.txt via a
non-shell mechanism, then git commit -F /tmp/cm.txt).
plugin/conventions/commit-messages.md documents the heredoc pattern
as canonical. Skills (task-work, milestone-new, task-new, etc.) reference
it as the way to author multi-line commit bodies that survive zsh’s
glob-on-parens hazard. In a Claude Code sandbox without the right
Bash(...) permission entries, the pattern is silently denied — the
operator either has to grant more permission, or fall back to
single-line -m "..." and accept the prose loss.
Proposed
Section titled “Proposed”plugin/conventions/commit-messages.md documents BOTH the
heredoc pattern (canonical for shell users with full bash
allowances) AND a Claude Code sandbox fallback using the Write
tool to author a tempfile + git commit -F. The doc explicitly
names the Bash(...) permissions the heredoc requires and points
sandboxed operators at the Write+-F path. Skills that author
multi-line commits cross-reference the convention rather than
inlining either pattern.
Rejected alternatives:
- Option 1 alone (document permissions only). Leaves sandboxed operators without a workable path; they still hit the denial and fall back to single-line commits.
- Option 2 alone (replace heredoc with Write+
-F). Drops the idiomatic shell shape for operators outside Claude Code where the heredoc is fine. The fallback is for sandboxed contexts, not a general replacement.
Approach
Section titled “Approach”- Edit
plugin/conventions/commit-messages.md:- Add a
## Permissions (heredoc shape)section listing the specificBash(...)entries Claude Code projects need for the heredoc pattern to work (at minimumBash(mktemp:*),Bash(cat:*), and theBash(git:*)entries that already work). - Add a
## Sandbox fallback (Write + git commit -F)section documenting the alternative: use theWritetool to create/tmp/<unique>.txtcontaining the commit body, thengit commit -F /tmp/<unique>.txt. Show the exact shape skill prose should use.
- Add a
- Cross-reference the new sections from the consuming skills’
Notes blocks (
task-work,task-close-out,task-new,milestone-new,entities-migrate,import-planning). Each reference is one bullet per skill perplugin/skills/CLAUDE.md’s shared-reference shape. - Add a project-check assertion: every SKILL.md that authors
multi-line commits links to
plugin/conventions/commit-messages.md(lint via the existing skill-prose linter rather than a new check).
Files to touch
Section titled “Files to touch”plugin/conventions/commit-messages.md— primary update.plugin/skills/*/SKILL.md— cross-reference updates as needed.
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: A Claude Code operator following the convention from a clean sandbox can author a multi-line commit message without the sandbox denying the operation.
- AC-2: The convention doc explicitly addresses sandbox constraints (either by listing the required permissions or by documenting a sandbox-friendly fallback).
Out of scope
Section titled “Out of scope”- Re-authoring all historical multi-line commits.
- Modifying the harness itself (out of repo).
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of T-6D5U-add-astro-docs-site on 2026-05-20.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-21. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: agent-manual — verified by reading the new “Sandbox fallback
(Write +
git commit -F)” section inplugin/conventions/commit-messages.md; the fallback uses theWritetool (gated by Write permission, not Bash) to author the message and only requires the existingBash(git commit:*)permission to land the commit. Dogfooded in this run: the implementation commit and post-mortem commit were both authored viaWrite /tmp/sdlc-commit-*.txtfollowed bygit commit -F, with no Bash heredoc invocations. - AC-2: auto — both new sections (“Permissions (heredoc shape)” and
“Sandbox fallback (Write +
git commit -F)”) are present in the convention doc; the existinglint_skill_prose.pyenforces that every commit-authoring skill references the doc, so the addressing cannot silently regress out of the consuming skills.
What worked
Section titled “What worked”- The shared-reference convention in
plugin/skills/CLAUDE.mdis already strong enough that 4 of 6 “consuming skills” listed in the Approach already had the cross-reference; the task only needed to add invariants, not new prose. lint_skill_prose.py+ per-skillinvariants.yamlis exactly the right shape for AC-3 — adding a one-phrase invariant per commit-authoring skill turned the “every SKILL.md links to the convention” assertion into a mechanically enforced gate without writing a new check_*.py.- The Write+
git commit -Fpattern worked end-to-end on the first try in this run (used for both the implementation commit and this post-mortem commit). The convention doc now reflects a path that has actually shipped a commit.
Friction and automation gaps
Section titled “Friction and automation gaps”start_task.py’sgit rebase mainstep inside the worktree hit a trivial conflict (main’sstatus: in-progressvs the feat branch’sstatus: open/readyfrom the ensure-ready commit) on the same task-file line. The script left the worktree in REBASE state per its exit-3 contract, but the conflict was a 100% predictable three-linestatus:adjacent-edit —start_task.pycould detect this exact shape (both sides edit only the task-file’sstatus:line; main’s value isin-progress; feat’s is anything in the pre-implementation set) and auto-resolve it toin-progressrather than bouncing to the operator. Worth a follow-up task to letstart_task.pyresolve the well-known task-file rebase conflict automatically. → T-H0W9-task-work-rebase-frontmatter-conflict- The task’s Approach Step 2 listed six “consuming skills”
(
task-work,task-close-out,task-new,milestone-new,entities-migrate,import-planning) needing cross-references, buttask-newandmilestone-newdon’t actually author commits (they shell out tonew_task.py/new_milestone.pyfor file creation; no model-derived commit messages). Two of the six were the wrong skills to list. This kind of “spec lists skills that don’t fit the criterion” friction is hard to catch automatically, but a lightweight gate could help: before opening a PR, grep each named “consuming skill” formktemp.*sdlc-commit\|git commit -Fand warn when the named skill doesn’t match. Same shape as thegit grepchecks the skill prose already gates on. → T-BKRP-task-ensure-ready-verifies-named-consuming-skills
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-BKRP-task-ensure-ready-verifies-named-consuming-skills — gate spec/codebase drift in consuming-skill lists (created)
- T-H0W9-task-work-rebase-frontmatter-conflict — auto-resolve start_task.py’s well-known status-line rebase conflict (linked existing)