jdx/mise · error

workspace provider attributed lockfile to unknown project

Error message

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

What it means

After confirming a lockfile attribution is in the provider's own namespace, the collector verifies each project ID actually exists in the workspace. If the provider attributes a lockfile to a namespaced-but-unknown project, this error fires, preventing lockfiles from being associated with nonexistent projects.

Solutions

  1. Remove the stale lockfile or recreate the missing project
  2. Re-run workspace discovery before lockfile attribution so the project map is fresh
  3. Fix the provider to derive lockfile→project IDs from the current discovered project list rather than from disk paths alone

Example fix

// before
let id = format!("{}:{}", self.id(), dir_name);
attribution.insert(lockfile, id);
// after
if let Some(p) = self.discovered.iter().find(|p| p.root == dir) {
    attribution.insert(lockfile, p.id.clone());
}
Defensive patterns

Strategy: validation

Validate before calling

for id in &ids {
    if !workspace.projects.contains_key(id) {
        eprintln!("skipping lockfile attribution to missing project {id}");
    }
}

Type guard

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

Try / catch

match attribute(lockfile, ids) {
    Ok(_) => (),
    Err(e) if e.to_string().contains("unknown project") => {
        eprintln!("lockfile references removed/renamed project; clean up or rediscover");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A provider maps a lockfile to a project ID that discovery did not produce — e.g. the project directory was deleted, the project's local ID changed, or the provider derives the ID from a path that no longer matches discovery.

Common situations: Stale lockfile after a project rename (local part of the ID changed); a project excluded from config but its lockfile still present; race where discovery and lockfile scanning see different directory states.

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/1fe4ea259d15cf71. Report an issue: GitHub.

Appendix: source

Thrown at src/task/workspace.rs:971

        let mut affected = BTreeSet::new();
        for provider in providers {
            let Some(projects) =
                provider.affected_projects_for_lockfile(lockfile_path, before, after, self)?
            else {
                continue;
            };
            recognized = true;
            let expected_prefix = format!("{}:", provider.id());
            for id in projects {
                if !id.as_str().starts_with(&expected_prefix) {
                    bail!(
                        "workspace provider {:?} attributed lockfile {:?} to foreign project {id:?}",
                        provider.id(),
                        lockfile_path
                    );
                }
                if !self.projects.contains_key(&id) {
                    bail!(
                        "workspace provider {:?} attributed lockfile {:?} to unknown project {id:?}",
                        provider.id(),
                        lockfile_path
                    );
                }
                affected.insert(id);
            }
        }
        Ok(recognized.then_some(affected))
    }

    /// Summarizes provider failures retained during lenient task discovery.
    pub(crate) fn provider_discovery_error(&self) -> Option<String> {
        (!self.provider_errors.is_empty()).then(|| {
            self.provider_errors
                .iter()
                .map(|(provider, error)| format!("{provider}: {error}"))
                .collect::<Vec<_>>()

View on GitHub (pinned to 533346cc37)