Summary
AgentDefaultsEditor discards the backend's validation message and shows a bare "Couldn't save." The backend's reason is accurate and actionable — it just never reaches the user.
Result: a save that can never succeed, with no indication of why, anywhere. No app log, no relay log (even at DEBUG), no config file touched.
Reproduce
- Put a derived provider/model key into the global agent config's
env_vars — e.g. GOOSE_PROVIDER or GOOSE_MODEL (easy to do if you have edited global-agent-config.json directly, e.g. as a workaround for a provider issue).
- Open Agents → Agent defaults, change the harness or model, press Save defaults.
- Every save fails with
Couldn't save. and no further detail.
Cause
The backend correctly rejects it — desktop/src-tauri/src/managed_agents/global_config/mod.rs:113:
if !derived.is_empty() {
return Err(format!(
"the following keys must be set via the structured provider/model fields, \
not as env vars: {}",
derived.join(", ")
));
}
against DERIVED_PROVIDER_MODEL_ENV_KEYS (GOOSE_MODEL, GOOSE_PROVIDER, BUZZ_AGENT_MODEL, BUZZ_AGENT_PROVIDER).
The rule itself is right — Buzz derives those vars from the structured fields, so allowing both would create two sources of truth. The problem is purely that the message is thrown away in desktop/src/features/agents/ui/AgentDefaultsEditor.tsx:233:
} catch (err) {
setSaveState("error");
setSaveError(typeof err === "string" ? err : "Couldn't save.");
}
set_global_agent_config returns Result<GlobalAgentConfigSaveResult, String>, so the reason is a string on the Rust side — but whatever reaches the catch fails the typeof err === "string" test (Tauri appears to wrap it), so the fallback always wins.
Suggested fix
Unwrap the Tauri error rather than testing it as a bare string, e.g.:
const message =
typeof err === "string" ? err :
(err as { message?: string })?.message ?? "Couldn't save.";
setSaveError(message);
Even surfacing String(err) would be a large improvement over the current dead end.
Impact
The validation is a guard rail users are quite likely to hit — anyone who has hand-edited global-agent-config.json to work around a provider problem will land on it. As shipped, the only way to discover the cause is to read the Rust source.
Environment
- Buzz Desktop 0.5.x, macOS arm64
- Relay: self-hosted, built from source
Related
Same family as #3636 and #4278 — a correct signal produced internally, then discarded before it reaches the operator.
Summary
AgentDefaultsEditordiscards the backend's validation message and shows a bare "Couldn't save." The backend's reason is accurate and actionable — it just never reaches the user.Result: a save that can never succeed, with no indication of why, anywhere. No app log, no relay log (even at DEBUG), no config file touched.
Reproduce
env_vars— e.g.GOOSE_PROVIDERorGOOSE_MODEL(easy to do if you have editedglobal-agent-config.jsondirectly, e.g. as a workaround for a provider issue).Couldn't save.and no further detail.Cause
The backend correctly rejects it —
desktop/src-tauri/src/managed_agents/global_config/mod.rs:113:against
DERIVED_PROVIDER_MODEL_ENV_KEYS(GOOSE_MODEL,GOOSE_PROVIDER,BUZZ_AGENT_MODEL,BUZZ_AGENT_PROVIDER).The rule itself is right — Buzz derives those vars from the structured fields, so allowing both would create two sources of truth. The problem is purely that the message is thrown away in
desktop/src/features/agents/ui/AgentDefaultsEditor.tsx:233:set_global_agent_configreturnsResult<GlobalAgentConfigSaveResult, String>, so the reason is a string on the Rust side — but whatever reaches thecatchfails thetypeof err === "string"test (Tauri appears to wrap it), so the fallback always wins.Suggested fix
Unwrap the Tauri error rather than testing it as a bare string, e.g.:
Even surfacing
String(err)would be a large improvement over the current dead end.Impact
The validation is a guard rail users are quite likely to hit — anyone who has hand-edited
global-agent-config.jsonto work around a provider problem will land on it. As shipped, the only way to discover the cause is to read the Rust source.Environment
Related
Same family as #3636 and #4278 — a correct signal produced internally, then discarded before it reaches the operator.