fix(883): canonical Bash(:*) syntax in scaffold settings template

Claude Code 2.1.123+ honours wildcard Bash allowlist patterns only in
the canonical form `Bash(cmd:*)`. The space form `Bash(cmd *)` falls
through to prompt_permission and gets auto-denied in agent mode,
breaking spawned coders.

- Rewrite all `Bash(cmd *)` patterns in STORY_KIT_CLAUDE_SETTINGS to
  the colon form.
- Replace separate `Bash(cargo build:*)` / `Bash(cargo check:*)` with
  a single `Bash(cargo:*)`.
- Add commonly-needed patterns: python3, node, npm, which, sed, awk,
  rg, diff, sort, uniq.
- Patch the live project-root .claude/settings.json so the running
  system picks up the fix immediately (rebuilt scaffolds will match).
- Add regression test asserting no `Bash(... *)` patterns survive and
  required common commands are present.
This commit is contained in:
dave
2026-04-30 13:44:51 +00:00
parent 801f9d8a26
commit 0e4a970e3a
3 changed files with 93 additions and 31 deletions
+27 -17
View File
@@ -1,28 +1,38 @@
{
"permissions": {
"allow": [
"Bash(cargo build:*)",
"Bash(cargo check:*)",
"Bash(git *)",
"Bash(ls *)",
"Bash(mkdir *)",
"Bash(mv *)",
"Bash(rm *)",
"Bash(touch *)",
"Bash(cargo:*)",
"Bash(git:*)",
"Bash(ls:*)",
"Bash(mkdir:*)",
"Bash(mv:*)",
"Bash(rm:*)",
"Bash(touch:*)",
"Bash(echo:*)",
"Bash(pwd *)",
"Bash(pwd:*)",
"Bash(grep:*)",
"Bash(find *)",
"Bash(head *)",
"Bash(tail *)",
"Bash(wc *)",
"Bash(cat *)",
"Bash(find:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(wc:*)",
"Bash(cat:*)",
"Bash(python3:*)",
"Bash(node:*)",
"Bash(npm:*)",
"Bash(which:*)",
"Bash(sed:*)",
"Bash(awk:*)",
"Bash(sort:*)",
"Bash(uniq:*)",
"Bash(diff:*)",
"Bash(rg:*)",
"Read",
"Edit",
"Write",
"Glob",
"Grep",
"mcp__huskies__*"
]
},
"enabledMcpjsonServers": [
"huskies"
]
"enabledMcpjsonServers": ["huskies"]
}
+23 -14
View File
@@ -70,22 +70,31 @@ setup wizard instructions and guide the user through it conversationally.\n";
pub(super) const STORY_KIT_CLAUDE_SETTINGS: &str = r#"{
"permissions": {
"allow": [
"Bash(cargo build:*)",
"Bash(cargo check:*)",
"Bash(git *)",
"Bash(ls *)",
"Bash(mkdir *)",
"Bash(mv *)",
"Bash(rm *)",
"Bash(touch *)",
"Bash(cargo:*)",
"Bash(git:*)",
"Bash(ls:*)",
"Bash(mkdir:*)",
"Bash(mv:*)",
"Bash(rm:*)",
"Bash(touch:*)",
"Bash(echo:*)",
"Bash(pwd *)",
"Bash(pwd:*)",
"Bash(grep:*)",
"Bash(find *)",
"Bash(head *)",
"Bash(tail *)",
"Bash(wc *)",
"Bash(cat *)",
"Bash(find:*)",
"Bash(head:*)",
"Bash(tail:*)",
"Bash(wc:*)",
"Bash(cat:*)",
"Bash(python3:*)",
"Bash(node:*)",
"Bash(npm:*)",
"Bash(which:*)",
"Bash(sed:*)",
"Bash(awk:*)",
"Bash(rg:*)",
"Bash(diff:*)",
"Bash(sort:*)",
"Bash(uniq:*)",
"Read",
"Edit",
"Write",
+43
View File
@@ -592,3 +592,46 @@ fn scaffold_does_not_overwrite_existing_project_toml_with_components() {
"scaffold should not overwrite existing project.toml"
);
}
#[test]
fn scaffold_story_kit_claude_settings_uses_canonical_bash_syntax() {
// Regression for bug 883. Claude Code 2.1.123+ honours wildcard Bash
// allowlist patterns only in the canonical form `Bash(cmd:*)`. The space
// form `Bash(cmd *)` falls through to prompt_permission and gets
// auto-denied in agent mode, breaking spawned coders.
let dir = tempdir().unwrap();
scaffold_story_kit(dir.path(), 3001).unwrap();
let settings = fs::read_to_string(dir.path().join(".claude/settings.json")).unwrap();
// No `Bash(... *)` (space before asterisk) patterns may survive.
let space_form = regex::Regex::new(r#""Bash\([^"]* \*\)""#).unwrap();
if let Some(m) = space_form.find(&settings) {
panic!(
"settings.json contains non-canonical Bash pattern: {} \
(use `Bash(cmd:*)` form instead)",
m.as_str()
);
}
// Common safe commands must be allowlisted in canonical form.
for required in &[
r#""Bash(cargo:*)""#,
r#""Bash(git:*)""#,
r#""Bash(ls:*)""#,
r#""Bash(cat:*)""#,
r#""Bash(grep:*)""#,
r#""Bash(find:*)""#,
r#""Bash(python3:*)""#,
r#""Bash(node:*)""#,
r#""Bash(npm:*)""#,
r#""Bash(rg:*)""#,
r#""Bash(sed:*)""#,
r#""Bash(awk:*)""#,
] {
assert!(
settings.contains(required),
"settings.json missing required allowlist pattern: {required}"
);
}
}