From 9ff0ba1ba37db9bba75d56d2a6a3a85d8032f9ac Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 24 Jul 2026 12:47:07 +0300 Subject: [PATCH 1/4] fix(desktop): apply Claude Code model picks via ANTHROPIC_MODEL Wire the claude runtime's model_env_var so Edit Agent selections reach spawned sessions, and treat ANTHROPIC_MODEL as a derived key. Closes #2692 Signed-off-by: Taksh --- desktop/src-tauri/src/managed_agents/discovery.rs | 5 ++++- desktop/src-tauri/src/managed_agents/env_vars.rs | 1 + desktop/src-tauri/src/managed_agents/env_vars/tests.rs | 7 ++++--- desktop/src-tauri/src/managed_agents/runtime/tests.rs | 7 ++++--- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/desktop/src-tauri/src/managed_agents/discovery.rs b/desktop/src-tauri/src/managed_agents/discovery.rs index eecbf4de3e..c1c924a031 100644 --- a/desktop/src-tauri/src/managed_agents/discovery.rs +++ b/desktop/src-tauri/src/managed_agents/discovery.rs @@ -122,7 +122,10 @@ const KNOWN_ACP_RUNTIMES: &[KnownAcpRuntime] = &[ adapter_install_hint: "Buzz talks to the Claude Code CLI through an ACP adapter. Install it with: npm install -g @agentclientprotocol/claude-agent-acp.", skill_dir: Some(".claude/skills"), supports_acp_model_switching: false, - model_env_var: None, + // Claude Code reads ANTHROPIC_MODEL at process start (same key readiness + // already maps for anthropic). Without this, Edit Agent model picks never + // reach the spawned process (#2692). + model_env_var: Some("ANTHROPIC_MODEL"), provider_env_var: None, provider_locked: true, default_env: &[], diff --git a/desktop/src-tauri/src/managed_agents/env_vars.rs b/desktop/src-tauri/src/managed_agents/env_vars.rs index 592a5cbbd9..8c67a41325 100644 --- a/desktop/src-tauri/src/managed_agents/env_vars.rs +++ b/desktop/src-tauri/src/managed_agents/env_vars.rs @@ -25,6 +25,7 @@ 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. pub(crate) const DERIVED_PROVIDER_MODEL_ENV_KEYS: &[&str] = &[ + "ANTHROPIC_MODEL", "GOOSE_MODEL", "GOOSE_PROVIDER", "BUZZ_AGENT_MODEL", diff --git a/desktop/src-tauri/src/managed_agents/env_vars/tests.rs b/desktop/src-tauri/src/managed_agents/env_vars/tests.rs index cf57b12546..fa2c7a42e5 100644 --- a/desktop/src-tauri/src/managed_agents/env_vars/tests.rs +++ b/desktop/src-tauri/src/managed_agents/env_vars/tests.rs @@ -418,9 +418,9 @@ fn merged_env_drops_oversize_value() { // ── derived provider/model key filter ────────────────────────────── // -// Pack import must strip derived env keys (GOOSE_MODEL, GOOSE_PROVIDER, -// BUZZ_AGENT_MODEL, BUZZ_AGENT_PROVIDER) so they don't shadow the -// structured AgentDefinition.model / AgentDefinition.provider fields after +// Pack import must strip derived env keys (ANTHROPIC_MODEL, GOOSE_MODEL, +// GOOSE_PROVIDER, BUZZ_AGENT_MODEL, BUZZ_AGENT_PROVIDER) so they don't shadow +// the structured AgentDefinition.model / AgentDefinition.provider fields after // the user edits them in the UI. #[test] @@ -439,6 +439,7 @@ fn is_derived_key_is_case_insensitive() { assert!(is_derived_provider_model_key("Goose_Provider")); assert!(is_derived_provider_model_key("buzz_agent_model")); assert!(is_derived_provider_model_key("BUZZ_AGENT_PROVIDER")); + assert!(is_derived_provider_model_key("anthropic_model")); } #[test] diff --git a/desktop/src-tauri/src/managed_agents/runtime/tests.rs b/desktop/src-tauri/src/managed_agents/runtime/tests.rs index 3f6ee996f6..19f041febc 100644 --- a/desktop/src-tauri/src/managed_agents/runtime/tests.rs +++ b/desktop/src-tauri/src/managed_agents/runtime/tests.rs @@ -534,14 +534,15 @@ fn runtime_metadata_env_vars_injects_model_and_provider() { #[test] fn runtime_metadata_env_vars_skips_provider_when_locked() { + // Claude: model via ANTHROPIC_MODEL; provider stays locked (no provider env). let vars = runtime_metadata_env_vars( - None, // claude has no model_env_var - None, // claude has no provider_env_var + Some("ANTHROPIC_MODEL"), + None, true, // provider_locked = true Some("claude-opus-4-7"), Some("anthropic"), ); - assert!(vars.is_empty()); + assert_eq!(vars, vec![("ANTHROPIC_MODEL", "claude-opus-4-7")]); } #[test] From b48ef63fa0338d11be9f4782b323143581a385b6 Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 24 Jul 2026 12:47:25 +0300 Subject: [PATCH 2/4] fix(desktop): keep agent avatars after restart on Agents screen Prefer the managed agent avatar URL before the profile query cache so custom icons don't flash to monograms on cold start. Closes #2576 Signed-off-by: Taksh --- .../agents/ui/UnifiedAgentsSection.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/desktop/src/features/agents/ui/UnifiedAgentsSection.tsx b/desktop/src/features/agents/ui/UnifiedAgentsSection.tsx index 34f9f9819f..6f0846feea 100644 --- a/desktop/src/features/agents/ui/UnifiedAgentsSection.tsx +++ b/desktop/src/features/agents/ui/UnifiedAgentsSection.tsx @@ -272,8 +272,14 @@ function AgentPersonaCard({ }); const isActive = agent ? isManagedAgentActive(agent) : false; const profileQuery = useUserProfileQuery(agent?.pubkey); + // Prefer the managed agent record — it survives restart even when the + // profile query cache is still empty (#2576). const avatarUrl = agent - ? firstAvatarUrl(persona.avatarUrl, profileQuery.data?.avatarUrl) + ? firstAvatarUrl( + agent.avatarUrl, + persona.avatarUrl, + profileQuery.data?.avatarUrl, + ) : persona.avatarUrl; const friendlyError = agent ? friendlyAgentLastError(agent.lastError, agent.lastErrorCode)?.copy @@ -361,6 +367,12 @@ function StandaloneAgentCard({ }) { const title = agent.name; const profileQuery = useUserProfileQuery(agent.pubkey); + // Prefer the managed agent record — it survives restart even when the + // profile query cache is still empty (#2576). + const avatarUrl = firstAvatarUrl( + agent.avatarUrl, + profileQuery.data?.avatarUrl, + ); const friendlyError = friendlyAgentLastError( agent.lastError, agent.lastErrorCode, @@ -374,7 +386,7 @@ function StandaloneAgentCard({ avatar={ onStartAgent(agent.pubkey)} /> } - avatarUrl={profileQuery.data?.avatarUrl} + avatarUrl={avatarUrl} dataTestId={`managed-agent-${agent.pubkey}`} label={title} modelLabel={resolveAgentCardModelLabel({ From 710dd35dc8da9f0a585149d5a01e3d3508c64e5c Mon Sep 17 00:00:00 2001 From: Taksh Date: Sat, 25 Jul 2026 08:48:33 +0300 Subject: [PATCH 3/4] fix(desktop): share ANTHROPIC_MODEL env key across runtime sites Single const for discovery, derived-key filter, and readiness so the key cannot drift; pin claude runtime shape with a regression test. Signed-off-by: Taksh --- desktop/src-tauri/src/managed_agents/discovery.rs | 2 +- desktop/src-tauri/src/managed_agents/discovery/tests.rs | 9 +++++++++ desktop/src-tauri/src/managed_agents/env_vars.rs | 8 +++++++- desktop/src-tauri/src/managed_agents/readiness.rs | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/desktop/src-tauri/src/managed_agents/discovery.rs b/desktop/src-tauri/src/managed_agents/discovery.rs index c1c924a031..67461caf45 100644 --- a/desktop/src-tauri/src/managed_agents/discovery.rs +++ b/desktop/src-tauri/src/managed_agents/discovery.rs @@ -125,7 +125,7 @@ const KNOWN_ACP_RUNTIMES: &[KnownAcpRuntime] = &[ // Claude Code reads ANTHROPIC_MODEL at process start (same key readiness // already maps for anthropic). Without this, Edit Agent model picks never // reach the spawned process (#2692). - model_env_var: Some("ANTHROPIC_MODEL"), + model_env_var: Some(crate::managed_agents::ANTHROPIC_MODEL_ENV_KEY), provider_env_var: None, provider_locked: true, default_env: &[], diff --git a/desktop/src-tauri/src/managed_agents/discovery/tests.rs b/desktop/src-tauri/src/managed_agents/discovery/tests.rs index 1b587dca0e..9eac5b23bd 100644 --- a/desktop/src-tauri/src/managed_agents/discovery/tests.rs +++ b/desktop/src-tauri/src/managed_agents/discovery/tests.rs @@ -1921,3 +1921,12 @@ fn discovery_publish_path_drops_mid_flight_delete() { "discovery's publish must not resurrect a harness deleted mid-discovery" ); } + +/// 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). +#[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")); + assert!(claude.provider_locked); + assert_eq!(claude.provider_env_var, None); diff --git a/desktop/src-tauri/src/managed_agents/env_vars.rs b/desktop/src-tauri/src/managed_agents/env_vars.rs index 8c67a41325..69be4100ba 100644 --- a/desktop/src-tauri/src/managed_agents/env_vars.rs +++ b/desktop/src-tauri/src/managed_agents/env_vars.rs @@ -24,8 +24,14 @@ 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] = &[ - "ANTHROPIC_MODEL", + ANTHROPIC_MODEL_ENV_KEY, "GOOSE_MODEL", "GOOSE_PROVIDER", "BUZZ_AGENT_MODEL", diff --git a/desktop/src-tauri/src/managed_agents/readiness.rs b/desktop/src-tauri/src/managed_agents/readiness.rs index c053d933c5..05e2244806 100644 --- a/desktop/src-tauri/src/managed_agents/readiness.rs +++ b/desktop/src-tauri/src/managed_agents/readiness.rs @@ -479,7 +479,7 @@ fn buzz_agent_requirements(effective: &EffectiveAgentEnv) -> Vec { 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, }; From 532a10b775da54d670ef821143ce21ccdf790c56 Mon Sep 17 00:00:00 2001 From: Taksh Date: Wed, 29 Jul 2026 17:45:25 +0300 Subject: [PATCH 4/4] fix(desktop): keep Claude model env test under file-size ratchet Move the regression test into a sibling module so discovery/tests.rs does not grow past the over-limit baseline. Signed-off-by: Taksh --- desktop/src-tauri/src/managed_agents/discovery.rs | 5 +---- .../src-tauri/src/managed_agents/discovery/tests.rs | 11 +---------- .../discovery/tests/claude_model_env.rs | 9 +++++++++ desktop/src-tauri/src/managed_agents/runtime/tests.rs | 1 - 4 files changed, 11 insertions(+), 15 deletions(-) create mode 100644 desktop/src-tauri/src/managed_agents/discovery/tests/claude_model_env.rs diff --git a/desktop/src-tauri/src/managed_agents/discovery.rs b/desktop/src-tauri/src/managed_agents/discovery.rs index 67461caf45..bc6605e86c 100644 --- a/desktop/src-tauri/src/managed_agents/discovery.rs +++ b/desktop/src-tauri/src/managed_agents/discovery.rs @@ -122,10 +122,7 @@ const KNOWN_ACP_RUNTIMES: &[KnownAcpRuntime] = &[ adapter_install_hint: "Buzz talks to the Claude Code CLI through an ACP adapter. Install it with: npm install -g @agentclientprotocol/claude-agent-acp.", skill_dir: Some(".claude/skills"), supports_acp_model_switching: false, - // Claude Code reads ANTHROPIC_MODEL at process start (same key readiness - // already maps for anthropic). Without this, Edit Agent model picks never - // reach the spawned process (#2692). - model_env_var: Some(crate::managed_agents::ANTHROPIC_MODEL_ENV_KEY), + model_env_var: Some(crate::managed_agents::ANTHROPIC_MODEL_ENV_KEY), // #2692 provider_env_var: None, provider_locked: true, default_env: &[], diff --git a/desktop/src-tauri/src/managed_agents/discovery/tests.rs b/desktop/src-tauri/src/managed_agents/discovery/tests.rs index 9eac5b23bd..c3335424d5 100644 --- a/desktop/src-tauri/src/managed_agents/discovery/tests.rs +++ b/desktop/src-tauri/src/managed_agents/discovery/tests.rs @@ -780,7 +780,7 @@ fn probe_codex_acp_version_parses_full_semver_output() { "adapter output must parse to its full semantic version" ); } - +mod claude_model_env; mod codex_version; #[cfg(unix)] @@ -1921,12 +1921,3 @@ fn discovery_publish_path_drops_mid_flight_delete() { "discovery's publish must not resurrect a harness deleted mid-discovery" ); } - -/// 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). -#[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")); - assert!(claude.provider_locked); - assert_eq!(claude.provider_env_var, None); diff --git a/desktop/src-tauri/src/managed_agents/discovery/tests/claude_model_env.rs b/desktop/src-tauri/src/managed_agents/discovery/tests/claude_model_env.rs new file mode 100644 index 0000000000..5cf03994c1 --- /dev/null +++ b/desktop/src-tauri/src/managed_agents/discovery/tests/claude_model_env.rs @@ -0,0 +1,9 @@ +/// 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). +#[test] +fn claude_runtime_declares_anthropic_model_env_var() { + let claude = super::super::known_acp_runtime_exact("claude").expect("claude runtime registered"); + assert_eq!(claude.model_env_var, Some("ANTHROPIC_MODEL")); + assert!(claude.provider_locked); + assert_eq!(claude.provider_env_var, None); +} diff --git a/desktop/src-tauri/src/managed_agents/runtime/tests.rs b/desktop/src-tauri/src/managed_agents/runtime/tests.rs index 19f041febc..270c21e222 100644 --- a/desktop/src-tauri/src/managed_agents/runtime/tests.rs +++ b/desktop/src-tauri/src/managed_agents/runtime/tests.rs @@ -534,7 +534,6 @@ fn runtime_metadata_env_vars_injects_model_and_provider() { #[test] fn runtime_metadata_env_vars_skips_provider_when_locked() { - // Claude: model via ANTHROPIC_MODEL; provider stays locked (no provider env). let vars = runtime_metadata_env_vars( Some("ANTHROPIC_MODEL"), None,