Summary
Several CodeCannon skills pass multi-line --body content to gh via
--body "$(cat <<'EOF' ... EOF)". In Claude Code, this pattern breaks static
permission matching: the harness sees an embedded command substitution and
refuses to extend any Bash(gh:*) allow rule over it, so it falls back to a
Yes/No permission prompt on every invocation — with no "Allow always" option,
because there is no stable pattern to remember. Users who run these skills
frequently get interrupted once per PR creation and once per follow-up issue,
even with broad gh:* allow rules in place.
The simple gh calls in the same skills (gh pr view --json …, gh issue list,
gh pr merge, gh label create) are fine — they match Bash(gh:*) cleanly
and never prompt. Only the body-via-substitution calls are affected.
Affected locations
skills/submit-for-review.md lines ~127-135 — gh pr create in Step 6
skills/submit-for-review.md lines ~263-277 — gh issue create in the
follow-up issues step
skills/deploy.md — the gh pr create and gh release create blocks (please
audit; same $(cat <<'EOF') shape)
A quick audit: grep -rn '"\$(cat <<' skills/ will enumerate any remaining
instances.
Root cause
Claude Code's permission matcher is conservative by design: any Bash command
containing $(...), backticks, or a pipeline cannot be matched against a
prefix allow rule, because the substituted content could execute arbitrary
code. The safety fallback is a Yes/No prompt, which intentionally has no
"Allow always" button because there is no pattern the harness could confidently
remember.
This is a feature of the matcher, not a bug, so the fix has to happen in the
skill invocation shape rather than in user config.
Proposed fix
Write the body to a temp file first, then pass --body-file. This eliminates
the substitution and makes the gh command a clean prefix match.
Before:
gh pr create --base dev --title "Foo" --body "$(cat <<'EOF'
Description.
Closes #42
EOF
)"
After:
cat > /tmp/cc_pr_body.md <<'EOF'
Description.
Closes #42
EOF
gh pr create --base dev --title "Foo" --body-file /tmp/cc_pr_body.md
The heredoc still exists, but it now feeds cat > file, which is a simple
prefix match on its own. The gh invocation afterward contains only literal
flags, so Bash(gh:*) matches cleanly and users who have allowed gh get zero
prompts.
Equivalent pattern for gh release create --notes:
cat > /tmp/cc_release_notes.md <<'EOF'
...
EOF
gh release create v1.2.3 --notes-file /tmp/cc_release_notes.md
Impact
Before: CodeCannon users running /submit-for-review see 2 Yes/No prompts per
run (PR create + follow-up issue) that cannot be permanently suppressed, no
matter how they configure permissions.
After: The same runs go end-to-end with zero prompts as long as the user has
Bash(gh:*) and Bash(cat > /tmp/*:*) (or similar) allowed. The existing
simple gh calls already match the former; the latter is trivial to add.
Notes
- Temp file names should be namespaced (e.g.
cc_pr_body.md) to avoid colliding
with other tools writing to /tmp.
- Consider cleaning them up at the end of the skill for tidiness, though it's
not required for correctness.
- Same pattern applies to any other
--body "$(...)" / --notes "$(...)" /
--message "$(...)" uses across CodeCannon skills.
Summary
Several CodeCannon skills pass multi-line
--bodycontent toghvia--body "$(cat <<'EOF' ... EOF)". In Claude Code, this pattern breaks staticpermission matching: the harness sees an embedded command substitution and
refuses to extend any
Bash(gh:*)allow rule over it, so it falls back to aYes/No permission prompt on every invocation — with no "Allow always" option,
because there is no stable pattern to remember. Users who run these skills
frequently get interrupted once per PR creation and once per follow-up issue,
even with broad
gh:*allow rules in place.The simple gh calls in the same skills (
gh pr view --json …,gh issue list,gh pr merge,gh label create) are fine — they matchBash(gh:*)cleanlyand never prompt. Only the body-via-substitution calls are affected.
Affected locations
skills/submit-for-review.mdlines ~127-135 —gh pr createin Step 6skills/submit-for-review.mdlines ~263-277 —gh issue createin thefollow-up issues step
skills/deploy.md— thegh pr createandgh release createblocks (pleaseaudit; same
$(cat <<'EOF')shape)A quick audit:
grep -rn '"\$(cat <<' skills/will enumerate any remaininginstances.
Root cause
Claude Code's permission matcher is conservative by design: any Bash command
containing
$(...), backticks, or a pipeline cannot be matched against aprefix allow rule, because the substituted content could execute arbitrary
code. The safety fallback is a Yes/No prompt, which intentionally has no
"Allow always" button because there is no pattern the harness could confidently
remember.
This is a feature of the matcher, not a bug, so the fix has to happen in the
skill invocation shape rather than in user config.
Proposed fix
Write the body to a temp file first, then pass
--body-file. This eliminatesthe substitution and makes the
ghcommand a clean prefix match.Before:
After:
The heredoc still exists, but it now feeds
cat > file, which is a simpleprefix match on its own. The
ghinvocation afterward contains only literalflags, so
Bash(gh:*)matches cleanly and users who have allowed gh get zeroprompts.
Equivalent pattern for
gh release create --notes:Impact
Before: CodeCannon users running
/submit-for-reviewsee 2 Yes/No prompts perrun (PR create + follow-up issue) that cannot be permanently suppressed, no
matter how they configure permissions.
After: The same runs go end-to-end with zero prompts as long as the user has
Bash(gh:*)andBash(cat > /tmp/*:*)(or similar) allowed. The existingsimple gh calls already match the former; the latter is trivial to add.
Notes
cc_pr_body.md) to avoid collidingwith other tools writing to /tmp.
not required for correctness.
--body "$(...)"/--notes "$(...)"/--message "$(...)"uses across CodeCannon skills.