jdx/mise · error

workspace provider attributed lockfile to foreign project

Error message

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

What it means

When attributing a lockfile, a provider may only claim project IDs inside its own namespace — IDs starting with `{provider_id}:`. If the provider lists a foreign project ID, the collector bails, since namespaces must stay consistent for correct dependency/lockfile ownership.

Solutions

  1. Fix the provider to filter attributed IDs to those starting with `format!("{}:", self.id())`
  2. Ensure the provider builds lockfile→project mappings only from its own discovered projects
  3. Check for a hardcoded provider prefix that no longer matches `provider.id()`

Example fix

// before
let expected_prefix = "npm:"; // hardcoded
// after
let expected_prefix = format!("{}:", provider.id());
Defensive patterns

Strategy: validation

Validate before calling

let prefix = format!("{}:", provider.id());
let own: Vec<_> = ids.into_iter().filter(|id| id.starts_with(&prefix)).collect();

Type guard

fn is_own_project(id: &str, provider_id: &str) -> bool { id.starts_with(&format!("{provider_id}:")) }

Try / catch

match attribute(lockfile, ids) {
    Ok(_) => (),
    Err(e) if e.to_string().contains("foreign project") => {
        eprintln!("provider attributed lockfile outside its namespace; fix prefix logic");
        return Err(e);
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A provider implementation includes project IDs gathered from another provider (or global IDs) in its lockfile attribution, or constructs IDs with the wrong provider prefix.

Common situations: Sharing attribution code between two providers with the prefix from the wrong one; a copy-pasted provider whose `id()` changed but hardcoded prefix logic did not; merging attribution results across providers.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/task/workspace.rs:964

        lockfile_path: &Path,
        before: Option<&str>,
        after: Option<&str>,
    ) -> Result<Option<BTreeSet<ProjectId>>> {
        let mut providers = providers.to_vec();
        providers.sort_by(|left, right| left.id().cmp(right.id()));
        let mut recognized = false;
        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))
    }

View on GitHub (pinned to 533346cc37)