rust-lang/cargo · error

found a virtual manifest at

Error message

found a virtual manifest at `{}` instead of a package manifest

What it means

Thrown by `read_package` when the manifest at the given path is a virtual manifest (a workspace root `[workspace]` with no `[package]` section) instead of a real package manifest. Cargo expects a `[package]` table when constructing a `Package` object; virtual manifests have no package metadata.

Solutions

  1. Point the tool or dependency at the member crate's `Cargo.toml` inside the workspace, not the root.
  2. Add a `[package]` section to the manifest if it was intended to be a real package.
  3. Use the correct subdirectory path for the member crate.

Example fix

# Before — pointing at workspace root
cargo read-manifest ./Cargo.toml

# After — point at the member crate
cargo read-manifest ./crates/my-crate/Cargo.toml
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;
fn validate_manifest_is_package(path: &Path) -> Result<(), String> {
    let content = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
    if !content.contains("[package]") {
        return Err(format!("`{}` is a virtual manifest, not a package manifest", path.display()));
    }
    Ok(())
}

Prevention

When it happens

Trigger: Calling code that invokes `read_package` on a `Cargo.toml` that contains only a `[workspace]` section (no `[package]`). This happens internally when Cargo tries to load a workspace root as if it were a package.

Common situations: Pointing a dependency or tool at a workspace root `Cargo.toml` rather than a member crate's manifest; misconfiguring a path dependency to a workspace root; virtual manifest accidentally used where a package manifest is required.

Related errors


AI-assisted analysis of rust-lang/cargo@eb98b54bc9 (2026-08-11). Data as JSON: /api/errors/0f36bd938493e129. Report an issue: GitHub.

Appendix: source

Thrown at src/ops/cargo_read_manifest.rs:22

use crate::util::errors::CargoResult;
use crate::workspace::parser::read_manifest;
use crate::workspace::{EitherManifest, Package, SourceId};
use tracing::trace;

pub fn read_package(
    path: &Path,
    source_id: SourceId,
    gctx: &GlobalContext,
) -> CargoResult<Package> {
    trace!(
        "read_package; path={}; source-id={}",
        path.display(),
        source_id
    );
    let manifest = read_manifest(path, source_id, gctx)?;
    let manifest = match manifest {
        EitherManifest::Real(manifest) => manifest,
        EitherManifest::Virtual(..) => anyhow::bail!(
            "found a virtual manifest at `{}` instead of a package \
             manifest",
            path.display()
        ),
    };

    Ok(Package::new(manifest, path))
}

View on GitHub (pinned to eb98b54bc9)