aaif-goose/goose · error
Required environment variable {} is not set
Error message
Required environment variable {} is not set What it means
Declarative (custom) providers expand `${VAR_NAME}` placeholders in string fields via expand_env_vars. Each var's EnvVarConfig says whether it is secret and required. When a placeholder is present, the var is marked required, has no default, and is found neither via Config::get_secret (secret vars) nor Config::get_param, expansion aborts naming the missing variable.
Source
Thrown at crates/goose/src/config/declarative_providers.rs:46
pub fn expand_env_vars(template: &str, env_vars: &[EnvVarConfig]) -> Result<String> {
let config = Config::global();
let mut result = template.to_string();
for var in env_vars {
let placeholder = format!("${{{}}}", var.name);
if !result.contains(&placeholder) {
continue;
}
let value = if var.secret {
config.get_secret::<String>(&var.name).ok()
} else {
config.get_param::<String>(&var.name).ok()
};
let value = match value {
Some(v) => v,
None => match &var.default {
Some(d) => d.clone(),
None if var.required => {
return Err(anyhow::anyhow!(
"Required environment variable {} is not set",
var.name
));
}
None => continue,
},
};
result = result.replace(&placeholder, &value);
}
Ok(result)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoadedProvider {
pub config: DeclarativeProviderConfig,
pub is_editable: bool,
}
View on GitHub (pinned to 3810898a74)
Solutions
- Store the value in goose config: plain vars via set_param / `goose config set --params MY_VAR value`, secret vars via the secret store (`goose configure`) so get_secret succeeds
- Or add a `default` to the env_vars entry in the provider JSON
- Or set `"required": false` if the placeholder is optional
- Check the placeholder text matches the env_vars name exactly, including case
Example fix
// before (custom provider JSON)
"base_url": "${ACME_BASE_URL}",
"env_vars": [{ "name": "ACME_BASE_URL", "required": true }]
// after
"base_url": "${ACME_BASE_URL}",
"env_vars": [{ "name": "ACME_BASE_URL", "required": true, "default": "https://api.acme.com/v1" }] Defensive patterns
Strategy: validation
Validate before calling
use goose::config::Config;
fn missing_required_placeholders(template: &str, env_vars: &[EnvVarConfig]) -> Vec<String> {
let config = Config::global();
env_vars
.iter()
.filter(|v| {
template.contains(&format!("${{{}}}", v.name))
&& v.default.is_none()
&& if v.secret {
config.get_secret::<String>(&v.name).is_err()
} else {
config.get_param::<String>(&v.name).is_err()
}
})
.map(|v| v.name.clone())
.collect()
}
let missing = missing_required_placeholders(&template, &env_vars);
if !missing.is_empty() { /* prompt for and store these vars before loading the provider */ } Try / catch
match expand_env_vars(&template, &env_vars) {
Err(e) if e.to_string().starts_with("Required environment variable") => {
let var = e.to_string().rsplit(' ').next().unwrap_or_default();
// collect `var` from the user, store it (set_secret/set_param), then retry
}
other => other?,
} Prevention
- Document every required var when distributing custom provider JSON
- Give non-secret placeholders a default in env_vars
- Validate the provider loads (goose configure) right after importing it on a new machine
When it happens
Trigger: Loading or using a custom provider whose JSON in the custom_providers dir contains `${MY_VAR}` in a field such as base_url, while its env_vars entry declares {name: MY_VAR, required: true} with no default, and MY_VAR was never stored in goose config (params for plain vars, secret store for secret vars).
Common situations: Sharing a custom provider JSON between machines without transferring the stored values; renaming the var in the JSON but not the stored config entry; deleting the secret later; marking a var required by mistake; placeholder spelling drift between template and env_vars name.
Related errors
- Provider not found: {}
- GOOSE_SERVER__SECRET_KEY must be set when using GOOSE_EXTERN
- {} must be at least 4096
- Invalid OPENAI_BASE_URL '{}': {}
- missing required key {}: {}
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/9fce26eb589fc672.
Report an issue: GitHub.