jdx/mise · error

unknown workspace project

Error message

unknown workspace project {id:?}

What it means

Methods that take a list of changed project IDs (e.g. to recompute affected projects/lockfiles) verify each ID exists in the workspace's project map. Passing an ID that was never discovered — or that no longer is — bails immediately rather than silently ignoring it.

Solutions

  1. Refresh the workspace (re-run discovery) so the project map is current before passing changed IDs
  2. Validate each changed ID against the workspace's known project IDs and drop unknown ones
  3. Fix the caller to only emit IDs obtained from `workspace.projects`

Example fix

// before
workspace.mark_changed(&["old-project"]);
// after
let ids: Vec<_> = changed.into_iter().filter(|id| workspace.projects.contains_key(id)).collect();
workspace.mark_changed(&ids);
Defensive patterns

Strategy: validation

Validate before calling

let known: Vec<_> = changed.iter().filter(|id| workspace.projects.contains_key(*id)).collect();
if known.len() != changed.len() {
    eprintln!("dropping unknown project ids from changed set");
}

Type guard

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

Try / catch

match workspace.mark_changed(&changed) {
    Ok(affected) => affected,
    Err(e) if e.to_string().contains("unknown workspace project") => {
        workspace = Workspace::reload()?; // refresh discovery, then retry
        workspace.mark_changed(&changed)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the affected-projects/changed-IDs API with an ID string not produced by workspace discovery, or with a stale ID after config/providers changed and the project was removed.

Common situations: Caching project IDs across runs while the user deleted a project or edited workspace config; a typo in the ID; watch-mode events firing for a directory that is no longer a recognized project.

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

Appendix: source

Thrown at src/task/workspace.rs:819

    pub(crate) fn affected_projects<'a>(
        &self,
        changed: impl IntoIterator<Item = &'a ProjectId>,
    ) -> Result<BTreeSet<ProjectId>> {
        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());
            }
        }

        let mut affected = BTreeSet::new();
        let mut pending = Vec::new();
        for id in changed {
            if !self.projects.contains_key(id) {
                bail!("unknown workspace project {id:?}");
            }
            if affected.insert(id.clone()) {
                pending.push(id.clone());
            }
        }

        while let Some(id) = pending.pop() {
            for dependent in dependents.get(&id).into_iter().flatten() {
                if affected.insert(dependent.clone()) {
                    pending.push(dependent.clone());
                }
            }
        }
        Ok(affected)
    }

    /// Maps changed paths to affected projects, including workspace-global inputs.
    ///

View on GitHub (pinned to 533346cc37)