From e32300d1f85633439c3616c5eb49fe558a7479ee Mon Sep 17 00:00:00 2001 From: dave Date: Sat, 11 Apr 2026 20:15:01 +0000 Subject: [PATCH] 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) --- server/src/agents/pty.rs | 8 ++++++-- server/src/http/mcp/diagnostics.rs | 16 ++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/server/src/agents/pty.rs b/server/src/agents/pty.rs index 62c7b572..8b551bfd 100644 --- a/server/src/agents/pty.rs +++ b/server/src/agents/pty.rs @@ -198,9 +198,13 @@ fn run_agent_pty_blocking( // and instead leak as unstructured PTY text. 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("bypassPermissions"); + cmd.arg("allowFullAutoEdit"); + cmd.arg("--permission-prompt-tool"); + cmd.arg("mcp__huskies__prompt_permission"); cmd.cwd(cwd); cmd.env("NO_COLOR", "1"); diff --git a/server/src/http/mcp/diagnostics.rs b/server/src/http/mcp/diagnostics.rs index f010d933..307f3996 100644 --- a/server/src/http/mcp/diagnostics.rs +++ b/server/src/http/mcp/diagnostics.rs @@ -150,14 +150,26 @@ pub(super) async fn tool_prompt_permission( let request_id = uuid::Uuid::new_v4().to_string(); 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 { request_id: request_id.clone(), tool_name: tool_name.clone(), tool_input: tool_input.clone(), 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;