jdx/mise · error

uv workspace member pattern

Error message

uv workspace member pattern {member:?} must be relative

What it means

uv workspace tool.uv.workspace.members entries are glob patterns that mise expands relative to the workspace root. Absolute paths cannot be joined into the escaped-root glob pattern, so mise rejects them up front rather than producing a broken pattern. uv itself also documents members as root-relative globs.

Solutions

  1. Change the member entry to a path relative to the workspace root, e.g. "packages/*".
  2. If the package lives outside the workspace directory, move it inside or use a symlink within the root.
  3. Use glob patterns like "members/*" or "." for the root project itself.

Example fix

# before
[tool.uv.workspace]
members = ["/home/me/proj/packages/*"]
# after
[tool.uv.workspace]
members = ["packages/*"]
Defensive patterns

Strategy: validation

Validate before calling

const members = doc['tool']?.['uv']?.['workspace']?.['members'] ?? [];
for (const m of members) {
  if (require('path').isAbsolute(m)) throw new Error(`member ${m} must be relative`);
}

Prevention

When it happens

Trigger: discover_members processes a members array entry like "/abs/path/pkg" or "C:\\pkg" where Path::is_absolute() is true.

Common situations: Copy-pasting an absolute path from `pwd` output into pyproject.toml; IDE auto-complete inserting full paths; generated configs from scripts that emitted absolute paths.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at src/task/workspace/uv.rs:294

        })
        .collect()
}

fn discover_members(
    pattern_root: &Path,
    canonical_root: &Path,
    members: &[String],
    excludes: &[Pattern],
    context: &WorkspaceDiscoveryContext,
) -> Result<BTreeSet<PathBuf>> {
    let options = match_options();
    // Avoid canonical Windows roots here because their verbatim `\\?\` prefix
    // contains a character that glob interprets as a wildcard.
    let escaped_root = Pattern::escape(&pattern_root.to_string_lossy());
    let mut roots = BTreeSet::new();
    for member in members {
        if Path::new(member).is_absolute() {
            bail!("uv workspace member pattern {member:?} must be relative");
        }
        let pattern = format!("{escaped_root}/{member}");
        for candidate in glob::glob_with(&pattern, options)
            .wrap_err_with(|| format!("invalid uv workspace member pattern {member:?}"))?
        {
            let candidate = candidate.wrap_err_with(|| {
                format!("failed to evaluate uv workspace member pattern {member:?}")
            })?;
            if !context.is_dir(&candidate) {
                continue;
            }
            let candidate = context.canonicalize(&candidate).wrap_err_with(|| {
                format!(
                    "failed to resolve uv workspace member {}",
                    candidate.display()
                )
            })?;
            let relative = relative_root(canonical_root, &candidate)?;

View on GitHub (pinned to 533346cc37)