{"record":{"id":"9fce26eb589fc672","repo":"aaif-goose/goose","slug":"required-environment-variable-is-not-set-9fce26","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/src/config/declarative_providers.rs","lineNumber":46,"sourceCode":"pub fn expand_env_vars(template: &str, env_vars: &[EnvVarConfig]) -> Result<String> {\n    let config = Config::global();\n    let mut result = template.to_string();\n    for var in env_vars {\n        let placeholder = format!(\"${{{}}}\", var.name);\n        if !result.contains(&placeholder) {\n            continue;\n        }\n        let value = if var.secret {\n            config.get_secret::<String>(&var.name).ok()\n        } else {\n            config.get_param::<String>(&var.name).ok()\n        };\n        let value = match value {\n            Some(v) => v,\n            None => match &var.default {\n                Some(d) => d.clone(),\n                None if var.required => {\n                    return Err(anyhow::anyhow!(\n                        \"Required environment variable {} is not set\",\n                        var.name\n                    ));\n                }\n                None => continue,\n            },\n        };\n        result = result.replace(&placeholder, &value);\n    }\n    Ok(result)\n}\n\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct LoadedProvider {\n    pub config: DeclarativeProviderConfig,\n    pub is_editable: bool,\n}\n","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/crates/goose/src/config/declarative_providers.rs#L28-L64","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before (custom provider JSON)\n\"base_url\": \"${ACME_BASE_URL}\",\n\"env_vars\": [{ \"name\": \"ACME_BASE_URL\", \"required\": true }]\n\n// after\n\"base_url\": \"${ACME_BASE_URL}\",\n\"env_vars\": [{ \"name\": \"ACME_BASE_URL\", \"required\": true, \"default\": \"https://api.acme.com/v1\" }]","handlingStrategy":"validation","validationCode":"use goose::config::Config;\n\nfn missing_required_placeholders(template: &str, env_vars: &[EnvVarConfig]) -> Vec<String> {\n    let config = Config::global();\n    env_vars\n        .iter()\n        .filter(|v| {\n            template.contains(&format!(\"${{{}}}\", v.name))\n                && v.default.is_none()\n                && if v.secret {\n                    config.get_secret::<String>(&v.name).is_err()\n                } else {\n                    config.get_param::<String>(&v.name).is_err()\n                }\n        })\n        .map(|v| v.name.clone())\n        .collect()\n}\n\nlet missing = missing_required_placeholders(&template, &env_vars);\nif !missing.is_empty() { /* prompt for and store these vars before loading the provider */ }","typeGuard":null,"tryCatchPattern":"match expand_env_vars(&template, &env_vars) {\n    Err(e) if e.to_string().starts_with(\"Required environment variable\") => {\n        let var = e.to_string().rsplit(' ').next().unwrap_or_default();\n        // collect `var` from the user, store it (set_secret/set_param), then retry\n    }\n    other => other?,\n}","preventionTips":["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"],"tags":["config","providers","custom-providers","environment"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}