rust-lang/cargo · critical

artifact-dir was not locked during clean

Error message

artifact-dir was not locked during clean

What it means

This is an internal Cargo invariant assertion (panic) in `clean_packages` (src/ops/cargo_clean.rs), reached while computing which uplifted artifact files to delete for each package target. `Layout::artifact_dir()` returns `Option` because Cargo only populates it when the layout was created with `must_take_artifact_dir_lock = true` (src/compiler/layout.rs:294-318); `cargo clean` always constructs its layouts with that flag set (cargo_clean.rs:200,206), so the expect firing means Cargo's own invariant broke — i.e., a Cargo bug, most plausibly in the `-Zbuild-dir-new-layout` code path that guards this block (cargo_clean.rs:278), not anything in your project.

Solutions

  1. Bypass cargo entirely: `rm -rf target` (or `cargo +stable clean`) removes the same directories without hitting the buggy path.
  2. Stop enabling the experimental flag: drop `-Zbuild-dir-new-layout` / `build-dir-new-layout` from your invocation, `CARGO_UNSTABLE_*` env, or `.cargo/config.toml` `[unstable]` table, then retry `cargo clean`.
  3. Update the nightly (`rustup update nightly`) — invariants like this are usually fixed quickly during the migration — and retest in a scratch copy of the workspace.
  4. If it still reproduces, report it to https://github.com/rust-lang/cargo/issues including `cargo +nightly version --verbose`, the exact command, and whether multiple cargo versions share the target dir.

Example fix

# before: panics with `artifact-dir was not locked during clean`
cargo +nightly -Zbuild-dir-new-layout clean

# after: same effect, no buggy code path
rm -rf target
# or
cargo +stable clean
Defensive patterns

Strategy: fallback

Validate before calling

# Guard scripts: refuse `cargo clean` while the experimental layout flag is on
if cargo +nightly -Zhelp 2>/dev/null | grep -q build-dir-new-layout \
   && grep -RqsE 'build-dir-new-layout|min-publish-age' .cargo/config.toml ~/.cargo/config.toml; then
  echo "unstable flags present — falling back to rm -rf target"
  rm -rf target
else
  cargo clean
fi

Prevention

When it happens

Trigger: Running `cargo clean` (optionally `-p <pkg>`) on a nightly toolchain with `-Z build-dir-new-layout` enabled (via CLI flag, `CARGO_UNSTABLE_BUILD_DIR_NEW_LAYOUT=1`, or an `[unstable]` config table), where a code regression or a layout constructed without the artifact-dir lock reaches the per-target uplift-removal loop at lines 299-306. Not reachable through normal user configuration of a released stable cargo.

Common situations: Nightly users experimenting with the build-directory re-layout migration sharing one `target/` dir across several cargo versions; cargo built from source or patched; IDE-embedded cargo (rust-analyzer) invoking a mismatched cargo on a workspace created by a different version; interrupted cleans leaving lock state half-updated.

Related errors


AI-assisted analysis of rust-lang/cargo@98a09e7e7d (2026-08-21). Data as JSON: /api/errors/4d571bcf1d0ce156. Report an issue: GitHub.

Appendix: source

Thrown at src/ops/cargo_clean.rs:307

            // Remove the uplifted copy.
            for target in pkg.targets() {
                if target.is_custom_build() {
                    continue;
                }
                let crate_name: Rc<str> = target.crate_name().into();
                for &mode in &[
                    CompileMode::Build,
                    CompileMode::Test,
                    CompileMode::Check { test: false },
                ] {
                    for (compile_kind, layout) in &layouts {
                        let triple = target_data.short_name(compile_kind);
                        let (file_types, _unsupported) = target_data
                            .info(*compile_kind)
                            .rustc_outputs(mode, target.kind(), triple)?;
                        let artifact_dir = layout
                            .artifact_dir()
                            .expect("artifact-dir was not locked during clean");
                        let uplift_dir = match target.kind() {
                            TargetKind::ExampleBin | TargetKind::ExampleLib(..) => {
                                Some(artifact_dir.examples())
                            }
                            // Tests/benchmarks are never uplifted.
                            TargetKind::Test | TargetKind::Bench => None,
                            _ => Some(artifact_dir.dest()),
                        };
                        if let Some(uplift_dir) = uplift_dir {
                            for file_type in file_types {
                                let uplifted_filename = file_type.uplift_filename(target);

                                // Dep-info generated by Cargo itself.
                                let dep_info = Path::new(&uplifted_filename)
                                    .with_extension("d")
                                    .to_string_lossy()
                                    .into_owned();

View on GitHub (pinned to 98a09e7e7d)