{"record":{"id":"e5ec0e62bc509ae7","repo":"aaif-goose/goose","slug":"required-environment-variable-is-not-set","errorCode":null,"errorMessage":"Required environment variable {} is not set","messagePattern":"Required environment variable (.+?) is not set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/goose-providers/src/declarative.rs","lineNumber":227,"sourceCode":"        std::env::var(key)\n    }\n}\n\nfn expand_env_vars(template: &str, env_vars: &[EnvVarConfig]) -> Result<String> {\n    let mut result = template.to_string();\n\n    for var in env_vars {\n        let placeholder = format!(\"${{{}}}\", var.name);\n        if !result.contains(&placeholder) {\n            continue;\n        }\n\n        let value = match std::env::var(&var.name) {\n            Ok(value) => value,\n            Err(_) => match &var.default {\n                Some(default) => default.clone(),\n                None if var.required => {\n                    anyhow::bail!(\"Required environment variable {} is not set\", var.name)\n                }\n                None => continue,\n            },\n        };\n\n        result = result.replace(&placeholder, &value);\n    }\n\n    Ok(result)\n}\n\nfn resolve_config(config: &mut DeclarativeProviderConfig) -> Result<()> {\n    if let Some(env_vars) = &config.env_vars {\n        config.base_url = expand_env_vars(&config.base_url, env_vars)?;\n\n        for var in env_vars {\n            if var.name.ends_with(\"_STREAMING\") {\n                let value = std::env::var(&var.name)","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose-providers/src/declarative.rs#L209-L245","documentation":"Declarative provider configs allow ${VAR} placeholders in base_url, base_path, and header values. resolve_placeholders walks the declared env_vars: when a template contains ${VAR}, std::env::var(VAR) fails, no 'default' is declared, and the entry is marked required: true, resolution aborts with this error naming the missing variable.","triggerScenarios":"A custom provider JSON whose base_url is e.g. \"https://${MY_GATEWAY_HOST}/v1\" with {\"name\": \"MY_GATEWAY_HOST\", \"required\": true} in env_vars, run in a shell/process where MY_GATEWAY_HOST is not exported (or exported only in a different session, systemd unit, or container layer).","commonSituations":"Config authored on one machine, executed by a service/CI where the env var isn't provisioned; var name case mismatch (env vars are case-sensitive); shell exports inside a script that exits before goose runs; pod/container missing the secret injection.","solutions":["Export the variable in the environment that actually runs goose: export MY_GATEWAY_HOST=... (check case and trailing whitespace)","Add a safe fallback in the JSON: {\"name\": \"MY_GATEWAY_HOST\", \"default\": \"https://fallback.example.com\", \"required\": false}","If the placeholder is optional, drop it from env_vars or set required: false so missing values just skip substitution","Print the process env (env | grep -i my_gateway) in the same context goose runs in to confirm visibility"],"exampleFix":"// before (provider.json)\n\"base_url\": \"https://${MY_GATEWAY_HOST}/v1\",\n\"env_vars\": [{\"name\": \"MY_GATEWAY_HOST\", \"required\": true}]\n\n// after\nexport MY_GATEWAY_HOST=api.internal.example.com   # in the launching shell/unit\n// or in JSON:\n\"env_vars\": [{\"name\": \"MY_GATEWAY_HOST\", \"required\": false, \"default\": \"https://api.internal.example.com\"}]","handlingStrategy":"validation","validationCode":"fn required_env_vars_present(templates: &[&str], vars: &[EnvVar]) -> anyhow::Result<()> {\n    let joined = templates.join(\" \");\n    for v in vars {\n        if joined.contains(&format!(\"${{{}}}\", v.name))\n            && v.required\n            && v.default.is_none()\n            && std::env::var(&v.name).is_err() {\n            anyhow::bail!(\"export {} before starting\", v.name);\n        }\n    }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"// Pre-flight env check at startup with an actionable message listing ALL missing\n// vars at once, instead of failing one-by-one during placeholder resolution:\nlet missing: Vec<_> = collect_missing_required_vars(&config);\nanyhow::ensure!(missing.is_empty(), \"missing env vars: {}\", missing.join(\", \"));","preventionTips":["Add a pre-flight env check (fail-fast, listing every missing var) before loading declarative providers","Give required vars a 'default' when a safe fallback exists so missing env degrades instead of aborting","Inject secrets via the service manager/environment files (systemd EnvironmentFile, k8s secrets) rather than manual exports"],"tags":["configuration","env-vars","json-config","provider-setup"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}