jdx/mise · error

lockfile attributed to unknown workspace project

Error message

lockfile {:?} attributed to unknown workspace project {id:?}

What it means

Lockfile-to-project attribution builds a map of which workspace project IDs each lockfile belongs to; every attributed ID must exist in the workspace. This error fires when a provider (or heuristic) maps a lockfile to a project ID the workspace does not know, so the lockfile cannot be assigned safely.

Solutions

  1. Delete the orphaned lockfile or restore the project it belonged to
  2. Re-run workspace discovery so project IDs are current
  3. Fix the provider's lockfile attribution to only emit IDs from its own discovered project list

Example fix

// before
ids = [format!("{}:{}", self.id(), remembered_name)];
// after
ids = discovered.iter().filter(|p| p.root == lockfile_dir).map(|p| p.id.clone()).collect();
Defensive patterns

Strategy: validation

Validate before calling

for (path, ids) in &lockfile_projects {
    for id in ids {
        if !workspace.projects.contains_key(id) {
            eprintln!("skipping {path:?}: unknown project {id}");
        }
    }
}

Type guard

fn is_known_project<'a>(ws: &Workspace, id: &'a str) -> bool { ws.projects.contains_key(id) }

Try / catch

match workspace.attribute_lockfiles(lockfile_projects) {
    Ok(p) => p,
    Err(e) if e.to_string().contains("attributed to unknown workspace project") => {
        eprintln!("stale lockfile or removed project; rediscover and retry");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A provider reports a lockfile whose recorded project ID is absent from the discovered project set — e.g. the project was removed/renamed but its lockfile remains, or the provider invents IDs not matching discovery.

Common situations: Deleted project directory with leftover package-lock.json / Cargo.lock still being scanned; a renamed project where the provider maps the lockfile by old name; config edited between discovery and lockfile scan.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/aee15d5858d4a51f. Report an issue: GitHub.

Appendix: source

Thrown at src/task/workspace.rs:904

                        .or_default()
                        .insert(AffectedProjectReason::ChangedPath { path: path.clone() });
                }
            }
        } else {
            for id in self.projects.keys() {
                projects.entry(id.clone()).or_default().extend(
                    global_paths
                        .iter()
                        .cloned()
                        .map(|path| AffectedProjectReason::GlobalPath { path }),
                );
            }
        }

        for (path, ids) in lockfile_projects {
            for id in ids {
                if !self.projects.contains_key(id) {
                    bail!(
                        "lockfile {:?} attributed to unknown workspace project {id:?}",
                        path
                    );
                }
                projects
                    .entry(id.clone())
                    .or_default()
                    .insert(AffectedProjectReason::Lockfile { path: path.clone() });
            }
        }

        let mut dependents = BTreeMap::<ProjectId, BTreeSet<ProjectId>>::new();
        for project in self.projects() {
            for dependency in &project.dependencies {
                dependents
                    .entry(dependency.clone())
                    .or_default()
                    .insert(project.id.clone());

View on GitHub (pinned to 533346cc37)