rust-lang/cargo · error

artifact-dir was not locked

Error message

artifact-dir was not locked

What it means

`CompilationFiles::output_dir` for doc/doc-scrape units calls `self.layout(unit.kind).artifact_dir().expect("artifact-dir was not locked")`. `artifact_dir()` returns `None` only when no `--artifact-dir` / uplift destination was acquired for that layout. Doc modes that hit this branch are expected to have locked the artifact dir; `None` means the build was configured without one yet still routed a doc unit through the artifact-dir branch — a Cargo-internal inconsistency.

Source

Thrown at src/compiler/build_runner/compilation_files.rs:223

        self.metas[unit]
    }

    /// Gets the short hash based only on the `PackageId`.
    /// Used for the metadata when `c_extra_filename` returns `None`.
    fn target_short_hash(&self, unit: &Unit) -> String {
        let hashable = unit.pkg.package_id().stable_hash(self.ws.root());
        util::short_hash(&(METADATA_VERSION, hashable))
    }

    /// Returns the directory where the artifacts for the given unit are
    /// initially created.
    pub fn output_dir(&self, unit: &Unit) -> PathBuf {
        // Docscrape units need to have doc/ set as the out_dir so sources for reverse-dependencies
        // will be put into doc/ and not into deps/ where the *.examples files are stored.
        if unit.mode.is_doc() || unit.mode.is_doc_scrape() {
            self.layout(unit.kind)
                .artifact_dir()
                .expect("artifact-dir was not locked")
                .doc()
                .to_path_buf()
        } else if unit.mode.is_doc_test() {
            panic!("doc tests do not have an out dir");
        } else if unit.target.is_custom_build() {
            self.build_script_dir(unit)
        } else if unit.target.is_example() && !self.ws.gctx().cli_unstable().build_dir_new_layout {
            self.layout(unit.kind).build_dir().examples().to_path_buf()
        } else if unit.artifact.is_true() {
            self.artifact_dir(unit)
        } else {
            self.deps_dir(unit).to_path_buf()
        }
    }

    /// Additional export directory from `--artifact-dir`.
    pub fn export_dir(&self) -> Option<PathBuf> {
        self.export_dir.clone()

View on GitHub (pinned to 0e07a15537)

Solutions

  1. Run `cargo clean` and retry `cargo doc`.
  2. Drop unstable layout flags (`-Zbuild-dir-new-layout`, `--artifact-dir`) to rule out a layout-routing bug.
  3. If reproducible on stock `cargo doc`, report it upstream with the manifest and target list.

Example fix

// before
self.layout(unit.kind)
    .artifact_dir()
    .expect("artifact-dir was not locked")
    .doc()
// after
let artifact = self.layout(unit.kind).artifact_dir().ok_or_else(|| {
    anyhow::anyhow!(
        "internal error: artifact dir not locked for doc unit `{}`",
        unit.pkg.package_id()
    )
})?;
artifact.doc()
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the target dir is not being modified concurrently and is writable
fn target_dir_healthy(p: &std::path::Path) -> bool {
    p.is_dir() && std::fs::metadata(p).map(|m| m.permissions().readonly() == false).unwrap_or(false)
}

Prevention

When it happens

Trigger: Running `cargo doc` (or doc-scrape for reverse-dependency docs) without an artifact directory set in a code path that assumes one exists; a profile/target combination where `Layout::artifact_dir` was not prepared but `output_dir` still chose the doc branch; misuse of `-Zunstable-options` around `--artifact-dir`.

Common situations: Custom profiles or unstable flags that change which layouts get prepared; building docs for a target whose `Layout` was created without locking artifacts; partial target-dir corruption.

Related errors


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