{"record":{"id":"d3a5ba9e7f379290","repo":"zeroclaw-labs/zeroclaw","slug":"environment-backed-auth-secret-references-an-empty","errorCode":null,"errorMessage":"environment-backed auth_secret references an empty environment variable name","messagePattern":"environment-backed auth_secret references an empty environment variable name","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/http_request.rs","lineNumber":451,"sourceCode":"    })?;\n    if value.is_empty() {\n        anyhow::bail!(\n            \"auth_secret '{secret_name}' references environment variable '{env_name}', but it is empty\"\n        );\n    }\n    Ok(Some(value))\n}\n\nfn env_secret_reference(raw_secret: &str) -> anyhow::Result<Option<&str>> {\n    let Some(inner) = raw_secret\n        .strip_prefix(\"${\")\n        .and_then(|value| value.strip_suffix('}'))\n    else {\n        return Ok(None);\n    };\n\n    if inner.is_empty() {\n        anyhow::bail!(\n            \"environment-backed auth_secret references an empty environment variable name\"\n        );\n    }\n    if !inner.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {\n        anyhow::bail!(\n            \"environment-backed auth_secret '{inner}' must contain only ASCII letters, numbers, or underscores\"\n        );\n    }\n    Ok(Some(inner))\n}\n\n#[async_trait]\nimpl Tool for HttpRequestTool {\n    fn name(&self) -> &str {\n        \"http_request\"\n    }\n\n    fn description(&self) -> &str {","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/http_request.rs#L433-L469","documentation":"Thrown by env_secret_reference (crates/zeroclaw-tools/src/http_request.rs:451) when a secret's config value is exactly \"${}\" — the environment-reference syntax with an empty variable name inside the braces. The parser strips ${ and } and then requires a non-empty identifier; an empty name can never be looked up, so it is rejected as a configuration typo rather than treated as a literal value.","triggerScenarios":"[http_request.secrets] api_token = \"${}\" in config.toml; templating that interpolates an unset variable name into the braces, producing \"${}\"; hand-editing that deleted the variable name; encrypting a placeholder \"${}\" value. Literal values that merely contain $ or braces but do not match the exact ${...} shape are treated as literals and do not trigger this.","commonSituations":"Config templates with a missing variable after rendering; secrets created by scripts whose variable-name argument was empty; copy-paste from docs leaving the placeholder unfilled.","solutions":["Fill in a real variable name: api_token = \"${API_TOKEN}\".","If you did not intend an env reference, drop the braces entirely and store the literal or encrypted value.","Validate rendered config templates for ${} placeholders before deployment."],"exampleFix":"# before\n[http_request.secrets]\napi_token = \"${}\"\n\n# after\n[http_request.secrets]\napi_token = \"${API_TOKEN}\"","handlingStrategy":"validation","validationCode":"fn env_reference_well_formed(raw: &str) -> bool {\n    match raw.strip_prefix(\"${\").and_then(|v| v.strip_suffix('}')) {\n        Some(inner) => !inner.is_empty(),\n        None => true, // literal or encrypted value, no reference to check\n    }\n}","typeGuard":null,"tryCatchPattern":"let result = tool.execute(args).await?;\nif let Some(err) = &result.error {\n    if err.contains(\"empty environment variable name\") {\n        // config typo: fill in the variable name inside ${...}\n    }\n}","preventionTips":["Lint config.toml for the literal \"${}\" placeholder after template rendering.","Never commit placeholder secrets; fail CI on unresolved template variables.","Distinguish intentional literals from references: only exact ${...} values are parsed as references."],"tags":["http","auth","secrets","configuration","zeroclaw"],"backgroundTag":"invalid-env-var-reference","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}