From 7dc422de9c96fec75817d0efcee7422a5f625057 Mon Sep 17 00:00:00 2001 From: TheArchitectit Date: Wed, 17 Jun 2026 12:55:49 -0500 Subject: [PATCH] fix(team): inject /setup-saved provider config for sub-agents Sub-agents failed because the main CLI calls inject_config_as_env_fallbacks() to set CLAWCUSTOMOPENAI_API_KEY / CLAWCUSTOMOPENAI_BASE_URL (and other provider env vars) before building its provider client, but the sub-agent spawn path in the tools crate never did. Custom providers set up via /setup were invisible to spawned agents, so they fell through to the Anthropic provider and died with 'missing Anthropic credentials'. Move inject_config_as_env_fallbacks from main.rs private fn into runtime/src/config.rs as a pub fn, export it via runtime::lib.rs, and call it at the top of run_agent_job() before the agent runtime is built. - runtime::inject_config_as_env_fallbacks(): idempotent (only sets env vars that aren't already present), reads provider config saved by /setup, maps provider kind -> env var names (including custom-openai -> CLAWCUSTOMOPENAI_*), injects api_key/base_url/model. - main.rs now calls runtime::inject_config_as_env_fallbacks() instead of the local private version. - tools::run_agent_job calls runtime::inject_config_as_env_fallbacks() before building the agent runtime so custom/exotic providers work for spawned agents too. Together with the earlier model-resolution and chain-fallback fixes (d8716efb), sub-agents now: (1) use the session's model by default, (2) inject the /setup-saved provider credentials, and (3) fall through to the next configured chain entry on a model-not-found 404. Co-Authored-By: Claude Fable 5 --- rust/crates/runtime/src/config.rs | 43 ++++++++++++++++++++++++ rust/crates/runtime/src/lib.rs | 6 ++-- rust/crates/rusty-claude-cli/src/main.rs | 2 +- rust/crates/tools/src/lib.rs | 7 ++++ 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/rust/crates/runtime/src/config.rs b/rust/crates/runtime/src/config.rs index c076e6dc..ae512ec4 100644 --- a/rust/crates/runtime/src/config.rs +++ b/rust/crates/runtime/src/config.rs @@ -2608,6 +2608,49 @@ fn deep_merge_objects( } } +/// Read the provider config saved by `/setup` and inject its credentials +/// into the environment so `ProviderClient::from_model()` can find them via +/// the env-var-based provider dispatch. Only sets vars that aren't already +/// present (preserves explicit env). Idempotent. +/// +/// Called by the main CLI at startup and by sub-agent threads in the tools +/// crate so that custom/ exotic providers work for spawned agents too. +pub fn inject_config_as_env_fallbacks() { + let cwd = std::env::current_dir().unwrap_or_default(); + let Ok(config) = ConfigLoader::default_for(&cwd).load() else { + return; + }; + let provider = config.provider(); + + // Map provider kind to the expected env var names + let (api_key_env, base_url_env) = match provider.kind().unwrap_or("anthropic") { + "anthropic" => ("ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL"), + "xai" => ("XAI_API_KEY", "XAI_BASE_URL"), + "openai" => ("OPENAI_API_KEY", "OPENAI_BASE_URL"), + "dashscope" => ("DASHSCOPE_API_KEY", "DASHSCOPE_BASE_URL"), + "custom-openai" => ("CLAWCUSTOMOPENAI_API_KEY", "CLAWCUSTOMOPENAI_BASE_URL"), + _ => return, // unknown provider kind — don't inject + }; + + // Only set env vars that aren't already set (preserve user's explicit env) + if let Some(api_key) = provider.api_key() { + if std::env::var(api_key_env).is_err() { + std::env::set_var(api_key_env, api_key); + } + } + if let Some(base_url) = provider.base_url() { + if std::env::var(base_url_env).is_err() { + std::env::set_var(base_url_env, base_url); + } + } + // Also inject the saved model so resolve_model_alias sees it + if let Some(model) = provider.model() { + if std::env::var("CLAWD_PROVIDER_MODEL").is_err() { + std::env::set_var("CLAWD_PROVIDER_MODEL", model); + } + } +} + #[cfg(test)] mod tests { use super::{ diff --git a/rust/crates/runtime/src/lib.rs b/rust/crates/runtime/src/lib.rs index 696395d0..50d8d3cc 100644 --- a/rust/crates/runtime/src/lib.rs +++ b/rust/crates/runtime/src/lib.rs @@ -68,9 +68,9 @@ pub use compact::{ get_compact_continuation_message, should_compact, CompactionConfig, CompactionResult, }; pub use config::{ - clear_user_provider_settings, default_config_home, save_user_provider_settings, - suppress_config_warnings_for_json_mode, ApiTimeoutConfig, ConfigEntry, ConfigError, - ConfigFileReport, ConfigFileStatus, ConfigInspection, ConfigLoader, ConfigSource, + clear_user_provider_settings, default_config_home, inject_config_as_env_fallbacks, + save_user_provider_settings, suppress_config_warnings_for_json_mode, ApiTimeoutConfig, ConfigEntry, + ConfigError, ConfigFileReport, ConfigFileStatus, ConfigInspection, ConfigLoader, ConfigSource, McpConfigCollection, McpInvalidServerConfig, McpManagedProxyServerConfig, McpOAuthConfig, McpRemoteServerConfig, McpSdkServerConfig, McpServerConfig, McpStdioServerConfig, McpTransport, McpWebSocketServerConfig, OAuthConfig, ProviderFallbackConfig, ResolvedPermissionMode, diff --git a/rust/crates/rusty-claude-cli/src/main.rs b/rust/crates/rusty-claude-cli/src/main.rs index 25697604..3d10c4f0 100644 --- a/rust/crates/rusty-claude-cli/src/main.rs +++ b/rust/crates/rusty-claude-cli/src/main.rs @@ -1018,7 +1018,7 @@ fn run() -> Result<(), Box> { // vars are preserved — resolution order remains: env var > .env file > // stored config. This only runs in the real binary (run() is not called by // unit tests) to avoid leaking user config into the test suite. - inject_config_as_env_fallbacks(); + runtime::inject_config_as_env_fallbacks(); match parse_args(&args)? { CliAction::DumpManifests { output_format, diff --git a/rust/crates/tools/src/lib.rs b/rust/crates/tools/src/lib.rs index 2918d0ab..aab2231f 100644 --- a/rust/crates/tools/src/lib.rs +++ b/rust/crates/tools/src/lib.rs @@ -5523,6 +5523,13 @@ fn teardown_agent_worktree(agent_id: &str, worktree_path: &std::path::Path) -> R } fn run_agent_job(job: &AgentJob) -> Result<(), String> { + // Inject provider credentials from /setup-saved config into env vars so + // sub-agents can reach the same endpoint the user connected the main session + // with (e.g. custom-openai's CLAWCUSTOMOPENAI_API_KEY / BASE_URL). Without + // this, sub-agents can't find custom/ or exotic providers that were set up + // via /setup rather than via explicit env vars. + runtime::inject_config_as_env_fallbacks(); + // Claim task if task_id is set (prevents duplicate work) if let Some(ref task_id) = job.task_id { if let Some(ref team_id) = job.team_id {