fix claude-code

This commit is contained in:
jaberjaber23
2026-03-10 02:14:49 +03:00
parent 56aeb499c9
commit f6f9cf7e9f
2 changed files with 109 additions and 39 deletions
+6 -1
View File
@@ -1143,7 +1143,12 @@ fn classify_streaming_error(err: &openfang_kernel::error::KernelError) -> String
}
}
llm_errors::LlmErrorCategory::Format => {
"LLM request failed. Check your API key and model configuration in Settings.".to_string()
// Claude Code CLI errors have actionable messages — pass them through
if inner.contains("Claude Code CLI") || inner.contains("claude auth") {
classified.raw_message.clone()
} else {
"LLM request failed. Check your API key and model configuration in Settings.".to_string()
}
}
_ => classified.sanitized_message,
}
@@ -12,13 +12,38 @@ use serde::Deserialize;
use tokio::io::AsyncBufReadExt;
use tracing::{debug, warn};
/// Environment variables safe to pass through to the Claude Code subprocess.
/// XDG_* and CLAUDE_* prefixes are handled dynamically.
const SAFE_ENV_PREFIXES: &[&str] = &[
"PATH", "HOME", "USER", "USERPROFILE", "TERM", "LANG", "SHELL",
"SYSTEMROOT", "PROGRAMFILES", "APPDATA", "LOCALAPPDATA", "TEMP", "TMP",
/// Environment variable names (and suffixes) to strip from the subprocess
/// to prevent leaking API keys from other providers. We keep the full env
/// intact (so Node.js, NVM, SSL, proxies, etc. all work) and only remove
/// secrets that belong to other LLM providers.
const SENSITIVE_ENV_EXACT: &[&str] = &[
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"GEMINI_API_KEY",
"GOOGLE_API_KEY",
"GROQ_API_KEY",
"DEEPSEEK_API_KEY",
"MISTRAL_API_KEY",
"TOGETHER_API_KEY",
"FIREWORKS_API_KEY",
"OPENROUTER_API_KEY",
"PERPLEXITY_API_KEY",
"COHERE_API_KEY",
"AI21_API_KEY",
"CEREBRAS_API_KEY",
"SAMBANOVA_API_KEY",
"HUGGINGFACE_API_KEY",
"XAI_API_KEY",
"REPLICATE_API_TOKEN",
"BRAVE_API_KEY",
"TAVILY_API_KEY",
"ELEVENLABS_API_KEY",
];
/// Suffixes that indicate a secret — remove any env var ending with these
/// unless it starts with `CLAUDE_`.
const SENSITIVE_SUFFIXES: &[&str] = &["_SECRET", "_TOKEN", "_PASSWORD"];
/// LLM driver that delegates to the Claude Code CLI.
pub struct ClaudeCodeDriver {
cli_path: String,
@@ -87,6 +112,30 @@ impl ClaudeCodeDriver {
_ => Some(stripped.to_string()),
}
}
/// Apply security env filtering to a command.
///
/// Instead of `env_clear()` (which breaks Node.js, NVM, SSL, proxies),
/// we keep the full environment and only remove known sensitive API keys
/// from other LLM providers.
fn apply_env_filter(cmd: &mut tokio::process::Command) {
for key in SENSITIVE_ENV_EXACT {
cmd.env_remove(key);
}
// Remove any env var with a sensitive suffix, unless it's CLAUDE_*
for (key, _) in std::env::vars() {
if key.starts_with("CLAUDE_") {
continue;
}
let upper = key.to_uppercase();
for suffix in SENSITIVE_SUFFIXES {
if upper.ends_with(suffix) {
cmd.env_remove(&key);
break;
}
}
}
}
}
/// JSON output from `claude -p --output-format json`.
@@ -141,7 +190,6 @@ impl LlmDriver for ClaudeCodeDriver {
let mut cmd = tokio::process::Command::new(&self.cli_path);
cmd.arg("-p")
.arg(&prompt)
.arg("--dangerously-skip-permissions")
.arg("--output-format")
.arg("json");
@@ -149,19 +197,7 @@ impl LlmDriver for ClaudeCodeDriver {
cmd.arg("--model").arg(model);
}
// SECURITY: Clear env and selectively re-add safe vars to prevent
// leaking API keys from other providers into the subprocess.
cmd.env_clear();
for key in SAFE_ENV_PREFIXES {
if let Ok(val) = std::env::var(key) {
cmd.env(key, val);
}
}
for (key, val) in std::env::vars() {
if key.starts_with("XDG_") || key.starts_with("CLAUDE_") {
cmd.env(key, val);
}
}
Self::apply_env_filter(&mut cmd);
cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::piped());
@@ -171,13 +207,41 @@ impl LlmDriver for ClaudeCodeDriver {
let output = cmd
.output()
.await
.map_err(|e| LlmError::Http(format!("Failed to spawn claude CLI: {e}")))?;
.map_err(|e| LlmError::Http(format!(
"Claude Code CLI not found or failed to start ({}). \
Install: npm install -g @anthropic-ai/claude-code && claude auth",
e
)))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
let detail = if !stderr.is_empty() { &stderr } else { &stdout };
let code = output.status.code().unwrap_or(1);
// Provide actionable error messages
let message = if detail.contains("not authenticated")
|| detail.contains("auth")
|| detail.contains("login")
|| detail.contains("credentials")
{
format!(
"Claude Code CLI is not authenticated. Run: claude auth\nDetail: {detail}"
)
} else if detail.contains("permission")
|| detail.contains("--dangerously-skip-permissions")
{
format!(
"Claude Code CLI requires permissions acceptance. \
Run: claude --dangerously-skip-permissions (once to accept)\nDetail: {detail}"
)
} else {
format!("Claude Code CLI exited with code {code}: {detail}")
};
return Err(LlmError::Api {
status: output.status.code().unwrap_or(1) as u16,
message: format!("Claude CLI failed: {stderr}"),
status: code as u16,
message,
});
}
@@ -225,7 +289,6 @@ impl LlmDriver for ClaudeCodeDriver {
let mut cmd = tokio::process::Command::new(&self.cli_path);
cmd.arg("-p")
.arg(&prompt)
.arg("--dangerously-skip-permissions")
.arg("--output-format")
.arg("stream-json")
.arg("--verbose");
@@ -234,19 +297,7 @@ impl LlmDriver for ClaudeCodeDriver {
cmd.arg("--model").arg(model);
}
// SECURITY: Clear env and selectively re-add safe vars to prevent
// leaking API keys from other providers into the subprocess.
cmd.env_clear();
for key in SAFE_ENV_PREFIXES {
if let Ok(val) = std::env::var(key) {
cmd.env(key, val);
}
}
for (key, val) in std::env::vars() {
if key.starts_with("XDG_") || key.starts_with("CLAUDE_") {
cmd.env(key, val);
}
}
Self::apply_env_filter(&mut cmd);
cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::piped());
@@ -255,7 +306,11 @@ impl LlmDriver for ClaudeCodeDriver {
let mut child = cmd
.spawn()
.map_err(|e| LlmError::Http(format!("Failed to spawn claude CLI: {e}")))?;
.map_err(|e| LlmError::Http(format!(
"Claude Code CLI not found or failed to start ({}). \
Install: npm install -g @anthropic-ai/claude-code && claude auth",
e
)))?;
let stdout = child
.stdout
@@ -455,4 +510,14 @@ mod tests {
let driver = ClaudeCodeDriver::new(Some(String::new()));
assert_eq!(driver.cli_path, "claude");
}
#[test]
fn test_sensitive_env_list_coverage() {
// Ensure all major provider keys are in the strip list
assert!(SENSITIVE_ENV_EXACT.contains(&"OPENAI_API_KEY"));
assert!(SENSITIVE_ENV_EXACT.contains(&"ANTHROPIC_API_KEY"));
assert!(SENSITIVE_ENV_EXACT.contains(&"GEMINI_API_KEY"));
assert!(SENSITIVE_ENV_EXACT.contains(&"GROQ_API_KEY"));
assert!(SENSITIVE_ENV_EXACT.contains(&"DEEPSEEK_API_KEY"));
}
}