jdx/mise · warning · eyre::Report

expected rustc output escapes its output directory

Error message

expected rustc output escapes its output directory

What it means

Before pairing cached outputs, validated_outputs() requires every expected output path (outputs.files plus outputs.dep_info) to have its parent exactly equal to outputs.directory (src/cache/rustc.rs:551-553). This bounds the restore destination: an output resolving outside the declared output directory would make the shim write restored files elsewhere, so the restore is refused. Unlike most errors here, this validates the current invocation, not cached data.

Source

Thrown at src/cache/rustc.rs:552

fn validated_outputs(
    directory: CacheDirectory,
    outputs: &RustcOutputs,
) -> Result<Vec<(CacheFileNode, PathBuf)>> {
    if directory.version != 1 || !directory.directories.is_empty() || !directory.symlinks.is_empty()
    {
        bail!("cached rustc output directory has unsupported entries");
    }
    let mut expected = outputs
        .files
        .iter()
        .chain(std::iter::once(&outputs.dep_info))
        .map(|path| {
            let name = path
                .file_name()
                .and_then(|name| name.to_str())
                .ok_or_else(|| eyre::eyre!("expected rustc output name is not UTF-8"))?;
            if path.parent() != Some(outputs.directory.as_path()) {
                bail!("expected rustc output escapes its output directory");
            }
            Ok((name.to_string(), path.clone()))
        })
        .collect::<Result<BTreeMap<_, _>>>()?;
    if directory.files.len() != expected.len() {
        bail!("cached rustc output set does not match the invocation");
    }
    let mut files = Vec::with_capacity(directory.files.len());
    for node in directory.files {
        validate_file_mode(&node)?;
        let destination = expected
            .remove(&node.name)
            .ok_or_else(|| eyre::eyre!("cached rustc output is unexpected: {}", node.name))?;
        files.push((node, destination));
    }
    if !expected.is_empty() {
        bail!("cached rustc output set is incomplete");
    }

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Inspect the rustc invocation cargo produces (cargo build -v) and check every -o/--out-dir/--emit path stays inside the target dir
  2. Remove or normalize custom RUSTFLAGS that place outputs outside the output directory; avoid symlinked target dirs for cached tasks
  3. If the invocation legitimately writes elsewhere, disable the rust action cache for that task (task rust_cache.enabled = false)

Example fix

# before: RUSTFLAGS push an output outside the output directory
export RUSTFLAGS='--emit=dep-info=../deps/x.d -o ../x.o'
# after: keep emitted files inside the output directory
export RUSTFLAGS='--emit=dep-info=x.d -o x.o'
Defensive patterns

Strategy: validation

Validate before calling

fn outputs_confined(outputs: &RustcOutputs) -> bool {
    outputs
        .files
        .iter()
        .chain(std::iter::once(&outputs.dep_info))
        .all(|p| p.parent() == Some(outputs.directory.as_path()))
}

Prevention

When it happens

Trigger: RustcInvocation::outputs() computes a path whose parent differs from the output directory: --out-dir joined with a component containing '..', an absolute -o pointing elsewhere, or symlinked working/target directories making the parent comparison fail.

Common situations: Custom RUSTFLAGS emitting files with absolute or parent-escaping paths; symlinked CARGO_TARGET_DIR or target dirs; build scripts invoking rustc directly with -o outside --out-dir; unusual out-dir spellings (trailing separators, normalized differently).

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/74762aa04710f626. Report an issue: GitHub.