From 261accfcd699fe1820c1ccf300f5816fddc587cc Mon Sep 17 00:00:00 2001 From: Gabe Hamilton Date: Thu, 21 May 2026 15:07:17 -0600 Subject: [PATCH 1/2] claude-code adapter: pass --dangerously-skip-permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The spawned `claude -p` subprocess has stdin=null, so it cannot answer permission prompts. Without this flag every tool call gets silently denied — stages degenerate into "partial_success with N tool errors" while doing no actual work. Verified against a local bootstrap run: before patch: install_sentrux ran 60s, status=partial_success, 17 tool errors after patch: install_sentrux ran 65s, status=success, 0 tool errors build_mock now actually writes its artifacts. fill_briefing actually reads and processes the briefing. End-to-end tool execution works. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/forge-llm/src/cli_adapters/claude_code.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/forge-llm/src/cli_adapters/claude_code.rs b/crates/forge-llm/src/cli_adapters/claude_code.rs index 179fd53..71fbc9e 100644 --- a/crates/forge-llm/src/cli_adapters/claude_code.rs +++ b/crates/forge-llm/src/cli_adapters/claude_code.rs @@ -44,7 +44,12 @@ impl ClaudeCodeAgentProvider { .arg(prompt) .arg("--output-format") .arg("stream-json") - .arg("--verbose"); + .arg("--verbose") + // The spawned session has stdin=null so it cannot answer + // permission prompts. Without this flag every tool call gets + // silently denied and the stage degenerates into "partial_success + // with N tool errors" without actually doing work. + .arg("--dangerously-skip-permissions"); let model = options .model_override From 0cd4fa31dba883f9136700d51a4830a39f551742 Mon Sep 17 00:00:00 2001 From: Gabe Hamilton Date: Thu, 21 May 2026 15:21:33 -0600 Subject: [PATCH 2/2] Make skip-permissions opt-in via AgentRunOptions::auto_approve_tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses gemini-code-assist[bot] PR #2 review feedback. Skipping permission prompts is a real security trade-off; library consumers should opt in explicitly. Changes: - Add `auto_approve_tools: bool` field to AgentRunOptions (defaults to false via derive(Default)). - ClaudeCodeAgentProvider only passes --dangerously-skip-permissions when options.auto_approve_tools is true. Default behavior matches upstream claude CLI (interactive permission prompts). - AgentProviderSubmitter (forge-attractor's pipeline runtime) sets auto_approve_tools=true because pipeline stages run with stdin=null by design — every stage would otherwise fail at the first tool call. - Doc comment on the field explains the trade-off and that the field maps to --dangerously-skip-permissions on the Claude Code adapter. Library consumers that wire up AgentProvider directly now have to make an explicit choice. The forge pipeline runtime is unchanged in observable behavior (still skips prompts as before). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/backends/agent_provider.rs | 4 ++++ crates/forge-llm/src/agent_provider.rs | 12 ++++++++++++ .../forge-llm/src/cli_adapters/claude_code.rs | 17 +++++++++++------ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/crates/forge-attractor/src/backends/agent_provider.rs b/crates/forge-attractor/src/backends/agent_provider.rs index f2d9a46..715805c 100644 --- a/crates/forge-attractor/src/backends/agent_provider.rs +++ b/crates/forge-attractor/src/backends/agent_provider.rs @@ -46,6 +46,10 @@ impl AgentSubmitter for AgentProviderSubmitter { model_override: options.model, reasoning_effort: options.reasoning_effort, system_prompt_override: options.system_prompt_override, + // Pipeline stages run with stdin=null by design. CLI adapters + // (Claude Code, etc.) need permission prompts skipped or every + // tool call gets silently denied. See AgentRunOptions doc. + auto_approve_tools: true, ..Default::default() }; diff --git a/crates/forge-llm/src/agent_provider.rs b/crates/forge-llm/src/agent_provider.rs index ce14b33..ee9f150 100644 --- a/crates/forge-llm/src/agent_provider.rs +++ b/crates/forge-llm/src/agent_provider.rs @@ -53,6 +53,17 @@ pub struct AgentRunOptions { pub env_vars: Option>, /// Real-time event callback for observability. pub on_event: Option>, + /// Skip per-tool-call permission prompts. + /// + /// Required for CLI adapters (Claude Code, Codex, Gemini) that run with + /// `stdin=null`: without this they hang at the first permission prompt + /// and every tool call gets silently denied. Defaults to `false` so + /// library consumers explicitly opt in; the pipeline runtime + /// (`AgentProviderSubmitter`) sets it to `true` because pipeline stages + /// run non-interactively by design. + /// + /// Maps to `--dangerously-skip-permissions` on the Claude Code adapter. + pub auto_approve_tools: bool, } impl std::fmt::Debug for AgentRunOptions { @@ -66,6 +77,7 @@ impl std::fmt::Debug for AgentRunOptions { .field("system_prompt_override", &self.system_prompt_override) .field("env_vars", &self.env_vars) .field("on_event", &self.on_event.as_ref().map(|_| "...")) + .field("auto_approve_tools", &self.auto_approve_tools) .finish() } } diff --git a/crates/forge-llm/src/cli_adapters/claude_code.rs b/crates/forge-llm/src/cli_adapters/claude_code.rs index 71fbc9e..d209760 100644 --- a/crates/forge-llm/src/cli_adapters/claude_code.rs +++ b/crates/forge-llm/src/cli_adapters/claude_code.rs @@ -44,12 +44,17 @@ impl ClaudeCodeAgentProvider { .arg(prompt) .arg("--output-format") .arg("stream-json") - .arg("--verbose") - // The spawned session has stdin=null so it cannot answer - // permission prompts. Without this flag every tool call gets - // silently denied and the stage degenerates into "partial_success - // with N tool errors" without actually doing work. - .arg("--dangerously-skip-permissions"); + .arg("--verbose"); + + // The spawned session has stdin=null so it cannot answer permission + // prompts. With auto_approve_tools=false (the default), every tool + // call gets silently denied — the stage looks "complete" but the + // tool_result events all have is_error=true and the agent reports + // partial_success without doing real work. Callers running stages + // non-interactively must opt in via AgentRunOptions::auto_approve_tools. + if options.auto_approve_tools { + cmd.arg("--dangerously-skip-permissions"); + } let model = options .model_override