fix: permission-prompt-tool response format and scaffold .claude/settings.json
- Return { behavior: "allow", updatedInput: <input> } 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 <noreply@anthropic.com>
This commit is contained in:
@@ -1816,7 +1816,7 @@ async fn tool_prompt_permission(args: &Value, ctx: &AppContext) -> Result<String
|
|||||||
.send(crate::http::context::PermissionForward {
|
.send(crate::http::context::PermissionForward {
|
||||||
request_id: request_id.clone(),
|
request_id: request_id.clone(),
|
||||||
tool_name: tool_name.clone(),
|
tool_name: tool_name.clone(),
|
||||||
tool_input,
|
tool_input: tool_input.clone(),
|
||||||
response_tx,
|
response_tx,
|
||||||
})
|
})
|
||||||
.map_err(|_| "No active WebSocket session to receive permission request".to_string())?;
|
.map_err(|_| "No active WebSocket session to receive permission request".to_string())?;
|
||||||
@@ -1834,11 +1834,10 @@ async fn tool_prompt_permission(args: &Value, ctx: &AppContext) -> Result<String
|
|||||||
.map_err(|_| "Permission response channel closed unexpectedly".to_string())?;
|
.map_err(|_| "Permission response channel closed unexpectedly".to_string())?;
|
||||||
|
|
||||||
if approved {
|
if approved {
|
||||||
// Claude Code SDK validates the response as a union:
|
// Claude Code SDK expects:
|
||||||
// { behavior: "allow" } | { behavior: "deny", message: string }
|
// Allow: { behavior: "allow", updatedInput: <record> }
|
||||||
// Previously we returned {"updatedInput": ...} which didn't match
|
// Deny: { behavior: "deny", message: string }
|
||||||
// either variant, causing intermittent `invalid_union` errors.
|
Ok(json!({"behavior": "allow", "updatedInput": tool_input}).to_string())
|
||||||
Ok(json!({"behavior": "allow"}).to_string())
|
|
||||||
} else {
|
} else {
|
||||||
slog_warn!("[permission] User denied permission for '{tool_name}'");
|
slog_warn!("[permission] User denied permission for '{tool_name}'");
|
||||||
Ok(json!({
|
Ok(json!({
|
||||||
@@ -3374,7 +3373,7 @@ stage = "coder"
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[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 tmp = tempfile::tempdir().unwrap();
|
||||||
let ctx = test_ctx(tmp.path());
|
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");
|
let parsed: Value = serde_json::from_str(&result).expect("result should be valid JSON");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed["behavior"], "allow",
|
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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,46 @@ parallel calls work fine.\n\
|
|||||||
\n\
|
\n\
|
||||||
Read .story_kit/README.md to see our dev process.\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]]
|
const DEFAULT_PROJECT_TOML: &str = r#"[[agent]]
|
||||||
name = "coder-1"
|
name = "coder-1"
|
||||||
stage = "coder"
|
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_script_if_missing(&script_root.join("test"), STORY_KIT_SCRIPT_TEST)?;
|
||||||
write_file_if_missing(&root.join("CLAUDE.md"), STORY_KIT_CLAUDE_MD)?;
|
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)?;
|
append_gitignore_entries(root)?;
|
||||||
|
|
||||||
// Run `git init` if the directory is not already a git repo, then make an initial commit
|
// 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")
|
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)
|
.current_dir(root)
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| format!("Failed to run git add: {}", e))?;
|
.map_err(|e| format!("Failed to run git add: {}", e))?;
|
||||||
|
|||||||
Reference in New Issue
Block a user