mirror of
https://github.com/RightNow-AI/openfang.git
synced 2026-07-30 06:32:17 +00:00
fix claude-code
This commit is contained in:
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user