astral-sh/uv · error · anyhow::Error

Extra names must start and end with a letter or digit and ma

Error message

Extra names must start and end with a letter or digit and may only contain -, _, ., and alphanumeric characters

What it means

`uv`'s `--extra` flags (uv pip/sync/add/tool/install etc., all wired through `extra_name_with_clap_error` at lib.rs:117) parse each comma-separated value with `ExtraName::from_str`. Extra names follow packaging normalization rules: they must begin and end with a letter or digit and may contain only `-`, `_`, `.`, and alphanumerics in between. On parse failure the clap value parser converts the error into this anyhow message.

Source

Thrown at crates/uv-cli/src/lib.rs:117

    /// Display the dependency graph as JSON.
    Json,
}

#[derive(Debug, Default, Clone, clap::ValueEnum)]
pub enum ListFormat {
    /// Display the list of packages in a human-readable table.
    #[default]
    Columns,
    /// Display the list of packages in a `pip freeze`-like format, with one package per line
    /// alongside its version.
    Freeze,
    /// Display the list of packages in a machine-readable JSON format.
    Json,
}

fn extra_name_with_clap_error(arg: &str) -> Result<ExtraName> {
    ExtraName::from_str(arg).map_err(|_err| {
        anyhow!(
            "Extra names must start and end with a letter or digit and may only \
            contain -, _, ., and alphanumeric characters"
        )
    })
}

// Configures Clap v3-style help menu colors
const STYLES: Styles = Styles::styled()
    .header(AnsiColor::Green.on_default().effects(Effects::BOLD))
    .usage(AnsiColor::Green.on_default().effects(Effects::BOLD))
    .literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD))
    .placeholder(AnsiColor::Cyan.on_default());

#[derive(Parser)]
#[command(name = "uv", author, long_version = crate::version::uv_self_version())]
#[command(about = "An extremely fast Python package manager.")]
#[command(
    after_help = "Use `uv help` for more details.",

View on GitHub (pinned to f1a42680ff)

Solutions

  1. Fix the name to match the pattern (letters/digits with `-._` allowed inside): `--extra dev --extra docs` or `--extra dev,docs`.
  2. Quote the argument in shell so spaces/special characters cannot split it: `--extra "dev-tools"`.
  3. Check the keys under `[project.optional-dependencies]` / `[tool.uv]` and use exactly those names.

Example fix

# before
uv sync --extra "dev tools"

# after
uv sync --extra dev --extra tools
Defensive patterns

Strategy: validation

Validate before calling

import re

EXTRA_RE = re.compile(r"^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$")

def valid_extra(name: str) -> bool:
    return bool(EXTRA_RE.match(name)) and "" not in name.split(",")

# use before invoking uv
extras = [e for e in "dev,docs".split(",") if e]
assert all(valid_extra(e) for e in extras), f"invalid extra name in {extras}"

Type guard

EXTRA_RE = re.compile(r"^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$")

def is_valid_extra_name(name: str) -> bool:
    """Narrow an extra name to uv's ExtraName rules: alnum start/end, -._ inside."""
    return isinstance(name, str) and bool(EXTRA_RE.match(name))

Prevention

When it happens

Trigger: Passing a malformed extra: `--extra "dev tools"` (space), `--extra 'dev!'`, `--extra -dev` (leading dash), `--extra dev.` (trailing dot), or an empty segment from a stray comma like `--extra dev,` (the value_delimiter ',' splits it into `""`).

Common situations: Unquoted shell arguments that split or glob; extras copied from pyproject keys that were never valid; comma lists with trailing separators; scripts building `--extra` values from unvalidated user input.

Related errors


AI-assisted analysis of astral-sh/uv@f1a42680ff (2026-08-16). Data as JSON: /api/errors/b993fd312c0771b6. Report an issue: GitHub.