fix: switch agent permission mode from bypassPermissions to allowFullAutoEdit

bypassPermissions ignored the worktree's .claude/settings.json entirely,
letting agents run any Bash command including cargo test (which they'd
spawn 4+ times concurrently, deadlocking on the build directory lock).

allowFullAutoEdit respects the settings.json allowlist, so agents can
only use the Bash commands we explicitly permit (cargo check, cargo
build, git) and must use MCP tools for everything else (run_tests,
run_lint, run_build).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dave
2026-04-11 20:15:01 +00:00
parent 32e36bbc4b
commit e32300d1f8
2 changed files with 20 additions and 4 deletions
+6 -2
View File
@@ -198,9 +198,13 @@ fn run_agent_pty_blocking(
// and instead leak as unstructured PTY text. // and instead leak as unstructured PTY text.
cmd.arg("--include-partial-messages"); cmd.arg("--include-partial-messages");
// Supervised agents don't need interactive permission prompts // Agents use allowFullAutoEdit so the worktree's .claude/settings.json
// controls which tools are pre-approved. Anything not in the allowlist
// triggers the permission prompt tool, which auto-denies for agents.
cmd.arg("--permission-mode"); cmd.arg("--permission-mode");
cmd.arg("bypassPermissions"); cmd.arg("allowFullAutoEdit");
cmd.arg("--permission-prompt-tool");
cmd.arg("mcp__huskies__prompt_permission");
cmd.cwd(cwd); cmd.cwd(cwd);
cmd.env("NO_COLOR", "1"); cmd.env("NO_COLOR", "1");
+14 -2
View File
@@ -150,14 +150,26 @@ pub(super) async fn tool_prompt_permission(
let request_id = uuid::Uuid::new_v4().to_string(); let request_id = uuid::Uuid::new_v4().to_string();
let (response_tx, response_rx) = tokio::sync::oneshot::channel(); let (response_tx, response_rx) = tokio::sync::oneshot::channel();
ctx.perm_tx // Try to forward to the interactive session (WebSocket/Matrix).
// If no session is active (headless agent), auto-deny the permission.
if ctx.perm_tx
.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.clone(), tool_input: tool_input.clone(),
response_tx, response_tx,
}) })
.map_err(|_| "No active WebSocket session to receive permission request".to_string())?; .is_err()
{
crate::slog!(
"[permission] Auto-denied '{tool_name}' (no interactive session — agent mode)"
);
return serde_json::to_string_pretty(&json!({
"behavior": "deny",
"message": format!("Permission denied for '{tool_name}'. Use the appropriate MCP tool instead (e.g. run_tests, run_build, run_lint).")
}))
.map_err(|e| format!("Serialization error: {e}"));
}
use crate::http::context::PermissionDecision; use crate::http::context::PermissionDecision;