{"record":{"id":"1ada6116dc085e0d","repo":"Hmbown/CodeWhale","slug":"invalid-environment-placeholder-in-mcp-config-valu","errorCode":null,"errorMessage":"invalid environment placeholder in MCP config value","messagePattern":"invalid environment placeholder in MCP config value","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp.rs","lineNumber":81,"sourceCode":"///\n/// On a missing or malformed placeholder the error names only the offending\n/// variable, never the surrounding value, so a secret-bearing string is never\n/// echoed into logs or error output.\nfn expand_env_placeholders_with(\n    value: &str,\n    environment: Option<&crate::plugins::HostEnvironment>,\n) -> Result<String> {\n    let mut out = String::new();\n    let mut rest = value;\n    while let Some(start) = rest.find(\"${\") {\n        out.push_str(&rest[..start]);\n        let after = &rest[start + 2..];\n        let Some(end) = after.find('}') else {\n            anyhow::bail!(\"unterminated environment placeholder in MCP config value\");\n        };\n        let name = &after[..end];\n        if name.is_empty() || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {\n            anyhow::bail!(\"invalid environment placeholder in MCP config value\");\n        }\n        let env_value = environment\n            .map_or_else(|| std::env::var(name), |env| env.var(name))\n            .with_context(|| {\n                format!(\"environment variable {name} required by MCP config is not set\")\n            })?;\n        out.push_str(&env_value);\n        rest = &after[end + 1..];\n    }\n    out.push_str(rest);\n    Ok(out)\n}\n\n#[cfg(test)]\nfn expand_env_placeholders(value: &str) -> Result<String> {\n    expand_env_placeholders_with(value, None)\n}\n","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/mcp.rs#L63-L99","documentation":"The placeholder parser found ${...} but the inner name fails validation: empty (${}) or containing characters outside ASCII letters, digits, and underscore. Names must be plain identifiers so environment lookup is unambiguous; substitution syntaxes from other tools are not supported.","triggerScenarios":"Values like ${}, ${MY-VAR}, ${VAR:-default}, ${var/cmd}, or placeholders containing whitespace - any inner text that is not a bare ASCII identifier.","commonSituations":"Copying docker-compose or bash substitution syntax (${VAR:-default}) into MCP config, hyphenated variable names, placeholders mangled by copy-paste from documentation.","solutions":["Use plain identifier names: letters, digits, underscore only, e.g. ${MY_VAR}","Express defaults by exporting the variable in the launching environment instead of inline default syntax","If you control the variable, rename it to an identifier-safe name"],"exampleFix":"# before\n\"url\": \"https://${host-name}/mcp\"\n# after: identifier-safe name, exported in the environment\nexport MCP_HOST_NAME=api.example.com\n\"url\": \"https://${MCP_HOST_NAME}/mcp\"","handlingStrategy":"validation","validationCode":"// Validate placeholder names at config load time\nfn placeholder_names_ok(v: &str) -> bool {\n    let mut rest = v;\n    while let Some(start) = rest.find(\"${\") {\n        let after = &rest[start + 2..];\n        let Some(end) = after.find('}') else { return false };\n        let name = &after[..end];\n        if name.is_empty() || !name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {\n            return false;\n        }\n        rest = &after[end + 1..];\n    }\n    true\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use UPPER_SNAKE environment variable names exclusively","Do not port shell or docker substitution syntax into MCP configs","Verify referenced variables are exported in the launching environment"],"tags":["mcp","config","env","validation","placeholders"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}