rust-lang/cargo · error · anyhow::Error

probably rustup rustc, but without rustup's env vars

Error message

probably rustup rustc, but without rustup's env vars

What it means

rustc_fingerprint detects that the resolved rustc path equals the rustup shim path (`maybe_rustup` true), meaning the compiler appears to be rustup-managed, but neither `RUSTUP_HOME` nor `RUSTUP_TOOLCHAIN` is set in the environment. Because rustup can swap the real compiler without touching the shim, Cargo cannot reliably fingerprint it without those vars, so it conservatively bails rather than risk a stale cache.

Source

Thrown at src/util/rustc.rs:394

    let maybe_rustup = rustup_rustc == rustc;
    match (
        maybe_rustup,
        gctx.get_env("RUSTUP_HOME"),
        gctx.get_env("RUSTUP_TOOLCHAIN"),
    ) {
        (_, Ok(rustup_home), Ok(rustup_toolchain)) => {
            debug!("adding rustup info to rustc fingerprint");
            rustup_toolchain.hash(&mut hasher);
            rustup_home.hash(&mut hasher);
            let real_rustc = Path::new(&rustup_home)
                .join("toolchains")
                .join(rustup_toolchain)
                .join("bin")
                .join("rustc")
                .with_extension(env::consts::EXE_EXTENSION);
            paths::mtime(&real_rustc)?.hash(&mut hasher);
        }
        (true, _, _) => anyhow::bail!("probably rustup rustc, but without rustup's env vars"),
        _ => (),
    }

    Ok(Hasher::finish(&hasher))
}

fn process_fingerprint(cmd: &ProcessBuilder, extra_fingerprint: u64) -> u64 {
    let mut hasher = StableHasher::new();
    extra_fingerprint.hash(&mut hasher);
    cmd.get_args().for_each(|arg| arg.hash(&mut hasher));
    let mut env = cmd.get_envs().iter().collect::<Vec<_>>();
    env.sort_unstable();
    env.hash(&mut hasher);
    Hasher::finish(&hasher)
}

View on GitHub (pinned to 0e07a15537)

Solutions

  1. Ensure `RUSTUP_HOME` and `RUSTUP_TOOLCHAIN` are set/forwarded in the environment (rustup normally exports them; run `rustup which rustc` to see expected values).
  2. Invoke cargo through rustup's normal entrypoint so env is populated, rather than calling the shim directly.
  3. Pin a direct (non-rustup) rustc via `build.rustc` in config.toml if you must run with a stripped environment.
  4. Re-run `rustup default <toolchain>` to repair the rustup setup.

Example fix

# before (env stripped)
env -i cargo build

# after (forward rustup env)
export RUSTUP_HOME="$(rustup show home)"
export RUSTUP_TOOLCHAIN="$(rustup show | awk '/Default Toolchain/{print $2}')"
cargo build
Defensive patterns

Strategy: fallback

Validate before calling

fn rustup_env_present() -> bool {
    std::env::var("RUSTUP_HOME").is_ok() && std::env::var("RUSTUP_TOOLCHAIN").is_ok()
}
// if false, either forward the env or pin build.rustc to a direct binary

Type guard

fn looks_like_rustup_rustc(rustc: &std::path::Path, rustup_rustc: &std::path::Path) -> bool {
    rustc == rustup_rustc
}

Try / catch

// Fallback: if fingerprinting bails on rustup env, re-run with env forwarded
// or pin a non-rustup rustc.
match rustc_fingerprint(...) {
    Err(e) if e.to_string().contains("without rustup's env vars") => {
        std::env::set_var("RUSTUP_HOME", home);
        std::env::set_var("RUSTUP_TOOLCHAIN", tc);
        // retry
    }
    r => r,
}

Prevention

When it happens

Trigger: Invoking cargo with a rustup-managed rustc on PATH but with rustup's env vars stripped — e.g. running under a sanitized/wrapper environment, a container that cleared env, calling the rustup shim directly without rustup's exported vars, or an IDE/launcher that filtered the environment.

Common situations: Containers/CI images that strip env vars but keep the rustup shim; `env -i` style sanitized runs; a custom launcher that invokes cargo without forwarding rustup's env; rustup installed but invoked through a proxy that drops env.

Related errors


AI-assisted analysis of rust-lang/cargo@0e07a15537 (2026-08-06). Data as JSON: /data/errors/5a4c410475cd17bf.json. Report an issue: GitHub.