Skip to content

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.

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.
  1. Edit plugin/conventions/commit-messages.md:
    • Add a ## Permissions (heredoc shape) section listing the specific Bash(...) entries Claude Code projects need for the heredoc pattern to work (at minimum Bash(mktemp:*), Bash(cat:*), and the Bash(git:*) entries that already work).
    • Add a ## Sandbox fallback (Write + git commit -F) section documenting the alternative: use the Write tool to create /tmp/<unique>.txt containing the commit body, then git commit -F /tmp/<unique>.txt. Show the exact shape skill prose should use.
  2. 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 per plugin/skills/CLAUDE.md’s shared-reference shape.
  3. 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).
  • plugin/conventions/commit-messages.md — primary update.
  • plugin/skills/*/SKILL.md — cross-reference updates as needed.
  • 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).
  • Re-authoring all historical multi-line commits.
  • Modifying the harness itself (out of repo).
  • none

Spawned by /sdlc:task-work post-mortem of T-6D5U-add-astro-docs-site on 2026-05-20.

Captured by /sdlc:task-work on 2026-05-21. PR: pending.

  • AC-1: agent-manual — verified by reading the new “Sandbox fallback (Write + git commit -F)” section in plugin/conventions/commit-messages.md; the fallback uses the Write tool (gated by Write permission, not Bash) to author the message and only requires the existing Bash(git commit:*) permission to land the commit. Dogfooded in this run: the implementation commit and post-mortem commit were both authored via Write /tmp/sdlc-commit-*.txt followed by git 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 existing lint_skill_prose.py enforces that every commit-authoring skill references the doc, so the addressing cannot silently regress out of the consuming skills.
  • The shared-reference convention in plugin/skills/CLAUDE.md is 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-skill invariants.yaml is 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 -F pattern 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.
  • start_task.py’s git rebase main step inside the worktree hit a trivial conflict (main’s status: in-progress vs the feat branch’s status: open/ready from 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-line status: adjacent-edit — start_task.py could detect this exact shape (both sides edit only the task-file’s status: line; main’s value is in-progress; feat’s is anything in the pre-implementation set) and auto-resolve it to in-progress rather than bouncing to the operator. Worth a follow-up task to let start_task.py resolve 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, but task-new and milestone-new don’t actually author commits (they shell out to new_task.py / new_milestone.py for 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” for mktemp.*sdlc-commit\|git commit -F and warn when the named skill doesn’t match. Same shape as the git grep checks the skill prose already gates on. → T-BKRP-task-ensure-ready-verifies-named-consuming-skills

← Back to Tasks