zeroclaw-labs/zeroclaw · error · anyhow::Error

grok_cli env_passthrough entry `{name}` is invalid; expected

Error message

grok_cli env_passthrough entry `{name}` is invalid; expected [A-Za-z_][A-Za-z0-9_]*

What it means

Every env_passthrough entry must match [A-Za-z_][A-Za-z0-9_]* after trimming; normalize_and_validate_env_passthrough rejects malformed names before the child environment is assembled, preventing garbled or injected env assignments.

Source

Thrown at crates/zeroclaw-providers/src/grok_cli.rs:521

    fn validate_acp_stdout_limit(value: Option<usize>) -> anyhow::Result<usize> {
        let limit = value.unwrap_or(acp::DEFAULT_ACP_STDOUT_LIMIT_BYTES);
        if !(acp::MIN_ACP_STDOUT_LIMIT_BYTES..=acp::MAX_ACP_STDOUT_LIMIT_BYTES).contains(&limit) {
            anyhow::bail!(
                "grok_cli max_acp_stdout_bytes must be between {} and {} bytes",
                acp::MIN_ACP_STDOUT_LIMIT_BYTES,
                acp::MAX_ACP_STDOUT_LIMIT_BYTES
            );
        }
        Ok(limit)
    }

    fn normalize_and_validate_env_passthrough(names: Vec<String>) -> anyhow::Result<Vec<String>> {
        let mut normalized: Vec<String> = Vec::with_capacity(names.len());
        for name in names {
            let name = name.trim();
            if !is_valid_env_var_name(name) {
                anyhow::bail!(
                    "grok_cli env_passthrough entry `{name}` is invalid; expected [A-Za-z_][A-Za-z0-9_]*"
                );
            }
            if is_disallowed_provider_env_var(name) {
                anyhow::bail!(
                    "grok_cli env_passthrough entry `{name}` is provider-owned \
                     (`XAI_*` other than `XAI_API_KEY`, and all `GROK_*`); \
                     other names (for example tool credentials) may be listed \
                     explicitly, and Grok CLI policy flags belong in `extra_args`"
                );
            }
            if !normalized
                .iter()
                .any(|existing| env_names_equal(existing.as_str(), name))
            {
                // Preserve the operator-supplied spelling; equality is
                // case-insensitive on Windows when checking membership later.
                normalized.push(name.to_string());

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Fix the entry to identifier form (letters, digits, underscore, not starting with a digit)
  2. Drop empty entries; entries are trimmed, so remove stray whitespace-only items
  3. If you meant a value assignment, use the CLI's own flag mechanism instead

Example fix

# before
env_passthrough = ["FOO-BAR", "1BAZ", ""]

# after
env_passthrough = ["FOO_BAR", "BAZ"]
Defensive patterns

Strategy: validation

Validate before calling

fn valid_env_var_name(name: &str) -> bool {
    let mut chars = name.chars();
    matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_')
        && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}

Type guard

fn valid_env_var_name(name: &str) -> bool {
    let mut chars = name.chars();
    matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_')
        && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}

Prevention

When it happens

Trigger: Entries like 'FOO BAR', '1FOO', 'FOO-BAR', values containing '=', or entries that are empty after trim; Windows-style '%VAR%' names pasted from cmd docs.

Common situations: YAML/TOML lists pasted with quotes or trailing spaces; names copied from a shell export line including the value; locale-specific characters in names.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/82050987d6e0bc98. Report an issue: GitHub.