From 80904dc2d181c3dac2450f3778810aa95d137722 Mon Sep 17 00:00:00 2001 From: Dave Date: Thu, 26 Feb 2026 19:02:11 +0000 Subject: [PATCH] fix: permission-prompt-tool response format and scaffold .claude/settings.json - Return { behavior: "allow", updatedInput: } from prompt_permission to match the Claude Code SDK expected format (was returning just { behavior: "allow" } which failed validation) - Scaffold .claude/settings.json with sensible permission defaults (Edit, Write, common Bash commands, mcp__story-kit__*) so fresh projects don't trigger constant permission prompts Co-Authored-By: Claude Opus 4.6 --- server/src/http/mcp.rs | 19 +++++++++------- server/src/io/fs.rs | 50 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/server/src/http/mcp.rs b/server/src/http/mcp.rs index 1988373..c15eca7 100644 --- a/server/src/http/mcp.rs +++ b/server/src/http/mcp.rs @@ -1816,7 +1816,7 @@ async fn tool_prompt_permission(args: &Value, ctx: &AppContext) -> Result Result } + // Deny: { behavior: "deny", message: string } + Ok(json!({"behavior": "allow", "updatedInput": tool_input}).to_string()) } else { slog_warn!("[permission] User denied permission for '{tool_name}'"); Ok(json!({ @@ -3374,7 +3373,7 @@ stage = "coder" } #[tokio::test] - async fn tool_prompt_permission_approved_returns_allow_behavior() { + async fn tool_prompt_permission_approved_returns_updated_input() { let tmp = tempfile::tempdir().unwrap(); let ctx = test_ctx(tmp.path()); @@ -3397,7 +3396,11 @@ stage = "coder" let parsed: Value = serde_json::from_str(&result).expect("result should be valid JSON"); assert_eq!( parsed["behavior"], "allow", - "approved must return behavior:allow for Claude Code SDK compatibility" + "approved must return behavior:allow" + ); + assert_eq!( + parsed["updatedInput"]["command"], "echo hello", + "approved must return updatedInput with original tool input for Claude Code SDK compatibility" ); } diff --git a/server/src/io/fs.rs b/server/src/io/fs.rs index 8b30a42..7429373 100644 --- a/server/src/io/fs.rs +++ b/server/src/io/fs.rs @@ -60,6 +60,46 @@ parallel calls work fine.\n\ \n\ Read .story_kit/README.md to see our dev process.\n"; +const STORY_KIT_CLAUDE_SETTINGS: &str = r#"{ + "permissions": { + "allow": [ + "Bash(cargo build:*)", + "Bash(cargo check:*)", + "Bash(cargo clippy:*)", + "Bash(cargo test:*)", + "Bash(cargo run:*)", + "Bash(cargo nextest run:*)", + "Bash(git *)", + "Bash(ls *)", + "Bash(mkdir *)", + "Bash(mv *)", + "Bash(rm *)", + "Bash(touch *)", + "Bash(echo:*)", + "Bash(pwd *)", + "Bash(pnpm install:*)", + "Bash(pnpm run build:*)", + "Bash(pnpm run test:*)", + "Bash(pnpm test:*)", + "Bash(pnpm build:*)", + "Bash(npm run build:*)", + "Bash(npx tsc:*)", + "Bash(npx vitest:*)", + "Bash(npx @biomejs/biome check:*)", + "Bash(npx playwright test:*)", + "Bash(script/test:*)", + "Bash(./script/test:*)", + "Edit", + "Write", + "mcp__story-kit__*" + ] + }, + "enabledMcpjsonServers": [ + "story-kit" + ] +} +"#; + const DEFAULT_PROJECT_TOML: &str = r#"[[agent]] name = "coder-1" stage = "coder" @@ -263,6 +303,14 @@ fn scaffold_story_kit(root: &Path) -> Result<(), String> { write_script_if_missing(&script_root.join("test"), STORY_KIT_SCRIPT_TEST)?; write_file_if_missing(&root.join("CLAUDE.md"), STORY_KIT_CLAUDE_MD)?; + // Create .claude/settings.json with sensible permission defaults so that + // Claude Code (both agents and web UI chat) can operate without constant + // permission prompts. + let claude_dir = root.join(".claude"); + fs::create_dir_all(&claude_dir) + .map_err(|e| format!("Failed to create .claude/ directory: {}", e))?; + write_file_if_missing(&claude_dir.join("settings.json"), STORY_KIT_CLAUDE_SETTINGS)?; + append_gitignore_entries(root)?; // Run `git init` if the directory is not already a git repo, then make an initial commit @@ -277,7 +325,7 @@ fn scaffold_story_kit(root: &Path) -> Result<(), String> { } let add_output = std::process::Command::new("git") - .args(["add", ".story_kit", "script", ".gitignore", "CLAUDE.md"]) + .args(["add", ".story_kit", "script", ".gitignore", "CLAUDE.md", ".claude"]) .current_dir(root) .output() .map_err(|e| format!("Failed to run git add: {}", e))?;