Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion desktop/src-tauri/src/managed_agents/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ const KNOWN_ACP_RUNTIMES: &[KnownAcpRuntime] = &[
adapter_install_hint: "Install the Claude Code ACP adapter via npm.",
skill_dir: Some(".claude/skills"),
supports_acp_model_switching: false,
model_env_var: None,
// The Claude CLI honors this env var as a session model override,
// and the adapter's child `claude` process inherits the spawn env —
// without this, the model picked in the UI is persisted but never
// applied (#2692).
model_env_var: Some(crate::managed_agents::ANTHROPIC_MODEL_ENV_KEY),
provider_env_var: None,
provider_locked: true,
default_env: &[],
Expand Down
22 changes: 22 additions & 0 deletions desktop/src-tauri/src/managed_agents/discovery/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1270,3 +1270,25 @@ fn test_install_shell_from_some_returns_path() {
"install_shell_from(Some) must return the path as Ok"
);
}

/// The claude runtime must declare a model env channel — without one, the
/// model picked in the UI is persisted but never applied at spawn (#2692).
/// ANTHROPIC_MODEL is the override the Claude CLI honors, and the same key
/// readiness uses for the anthropic provider.
#[test]
fn claude_runtime_declares_anthropic_model_env_var() {
let claude = super::known_acp_runtime_exact("claude").expect("claude runtime registered");
assert_eq!(claude.model_env_var, Some("ANTHROPIC_MODEL"));
// Provider stays locked: only the model is injectable for this harness.
assert!(claude.provider_locked);
assert_eq!(claude.provider_env_var, None);
}

/// A stale persisted ANTHROPIC_MODEL must not shadow the structured model
/// field after a UI edit — same treatment as GOOSE_MODEL / BUZZ_AGENT_MODEL.
#[test]
fn anthropic_model_is_a_derived_env_key() {
use crate::managed_agents::is_derived_provider_model_key;
assert!(is_derived_provider_model_key("ANTHROPIC_MODEL"));
assert!(is_derived_provider_model_key("anthropic_model"));
}
7 changes: 7 additions & 0 deletions desktop/src-tauri/src/managed_agents/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,18 @@ use std::collections::BTreeMap;
///
/// Non-structured knobs (`GOOSE_TEMPERATURE`, `GOOSE_CONTEXT_LIMIT`) are NOT
/// in this list — they have no structured counterpart and must be preserved.
/// Env var the Claude CLI honors as a session model override. Single source
/// of truth for the key — referenced by the claude runtime entry
/// (`discovery.rs`), the derived-key list below, and the readiness
/// provider-model mapping, so the three cannot drift apart.
pub(crate) const ANTHROPIC_MODEL_ENV_KEY: &str = "ANTHROPIC_MODEL";

pub(crate) const DERIVED_PROVIDER_MODEL_ENV_KEYS: &[&str] = &[
"GOOSE_MODEL",
"GOOSE_PROVIDER",
"BUZZ_AGENT_MODEL",
"BUZZ_AGENT_PROVIDER",
ANTHROPIC_MODEL_ENV_KEY,
];

/// Returns `true` if `key` is a derived provider/model env key that should be
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/managed_agents/readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ fn buzz_agent_requirements(effective: &EffectiveAgentEnv) -> Vec<Requirement> {
Some("databricks") | Some("databricks_v2") | Some("databricks-v2") => {
Some("DATABRICKS_MODEL")
}
Some("anthropic") => Some("ANTHROPIC_MODEL"),
Some("anthropic") => Some(super::ANTHROPIC_MODEL_ENV_KEY),
Some("openai") | Some("openai-compat") => Some("OPENAI_COMPAT_MODEL"),
_ => None,
};
Expand Down
12 changes: 10 additions & 2 deletions desktop/src-tauri/src/managed_agents/runtime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,13 +517,21 @@ fn runtime_metadata_env_vars_injects_model_and_provider() {

#[test]
fn runtime_metadata_env_vars_skips_provider_when_locked() {
// The claude runtime shape: model injectable via ANTHROPIC_MODEL (#2692),
// provider locked to Anthropic so no provider env is written.
let vars = runtime_metadata_env_vars(
None, // claude has no model_env_var
Some("ANTHROPIC_MODEL"),
None, // claude has no provider_env_var
true, // provider_locked = true
Some("claude-opus-4-7"),
Some("claude-sonnet-5"),
Some("anthropic"),
);
assert_eq!(vars, vec![("ANTHROPIC_MODEL", "claude-sonnet-5")]);
}

#[test]
fn runtime_metadata_env_vars_empty_without_env_channels() {
let vars = runtime_metadata_env_vars(None, None, true, Some("some-model"), Some("anthropic"));
assert!(vars.is_empty());
}

Expand Down