Hmbown/CodeWhale · error · anyhow::Error

invalid lane environment key {key:?}

Error message

invalid lane environment key {key:?}

What it means

Thrown by write_lane_environment when an environment key fails the identifier check: the first character must be '_' or an ASCII letter, and the rest must be '_' or ASCII alphanumeric ([A-Za-z_][A-Za-z0-9_]*). Values are unrestricted — only key names are validated. This matches what a shell can legally export, since the pairs are written as a private lane environment file.

Source

Thrown at crates/lane/src/runtime.rs:234

    match std::fs::remove_file(path) {
        Ok(()) => Ok(()),
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
        Err(err) => Err(err).with_context(|| format!("remove {}", path.display())),
    }
}

fn valid_environment_key(key: &str) -> bool {
    let mut chars = key.chars();
    chars
        .next()
        .is_some_and(|ch| ch == '_' || ch.is_ascii_alphabetic())
        && chars.all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
}

fn write_lane_environment(path: &Path, environment: &[(String, String)]) -> Result<()> {
    for (key, _) in environment {
        if !valid_environment_key(key) {
            bail!("invalid lane environment key {key:?}");
        }
    }
    let encoded = serde_json::to_vec(environment).context("serialize private lane environment")?;
    if encoded.len() as u64 > MAX_ENVIRONMENT_BYTES {
        bail!(
            "private lane environment exceeds {} bytes",
            MAX_ENVIRONMENT_BYTES
        );
    }

    let tmp_path = lane_environment_tmp_path(path);
    remove_file_if_present(path)?;
    remove_file_if_present(&tmp_path)?;
    let mut options = OpenOptions::new();
    options.create_new(true).write(true);
    #[cfg(unix)]
    {
        use std::os::unix::fs::OpenOptionsExt;

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Rename keys to identifier form: my-var -> MY_VAR, foo.bar -> FOO_BAR
  2. Filter/translate the environment at the boundary before building LaneStartSpec (map '-' and '.' to '_')
  3. Drop empty-string keys — they can never be valid

Example fix

// before
let spec = LaneStartSpec { environment: vec![("my-var".into(), "1".into())], .. };

// after
let spec = LaneStartSpec { environment: vec![("MY_VAR".into(), "1".into())], .. };
Defensive patterns

Strategy: type-guard

Validate before calling

fn valid_env_key(key: &str) -> bool {
    let mut chars = key.chars();
    chars.next().is_some_and(|c| c == '_' || c.is_ascii_alphabetic())
        && chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}

let environment: Vec<(String, String)> = environment
    .into_iter()
    .filter(|(k, _)| valid_env_key(k))
    .collect();

Type guard

fn is_valid_lane_env_key(key: &str) -> bool {
    let mut chars = key.chars();
    chars.next().is_some_and(|c| c == '_' || c.is_ascii_alphabetic())
        && chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}

Prevention

When it happens

Trigger: A LaneStartSpec.environment containing keys like "my-var" (hyphen), "123" (leading digit), "foo.bar" (dot), an empty string, or a non-ASCII/unicode key. Collecting the ambient environment of a tool that permits hyphenated names and passing it through unchanged triggers this on the first bad key.

Common situations: Forwarding a parent process env dump that includes keys other languages allow; machine-generated keys from config systems (dot-notation like server.port); placeholders like "-" or "" sneaking in from templating.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/9a88b229d65d839c. Report an issue: GitHub.