headroomlabs-ai/headroom · error · ValueError

expected one Ruff dev dependency, found {ruff_requirements!r

Error message

expected one Ruff dev dependency, found {ruff_requirements!r}

What it means

verify-ruff-version.py reads `project.optional-dependencies.dev` from pyproject.toml and requires exactly one requirement starting with `ruff`. Zero entries (Ruff dependency removed) or two-plus (e.g., both `ruff` and a `ruff-*` plugin pinned) fail, because the script cannot tell which one is the authoritative version pin.

Source

Thrown at scripts/verify-ruff-version.py:33

ROOT = Path(__file__).resolve().parent.parent
RUFF_PRE_COMMIT_REPO = "https://github.com/astral-sh/ruff-pre-commit"
WORKFLOW_VERSION_COMMAND = "python scripts/verify-ruff-version.py --print-version"
WORKFLOW_INSTALL_REFERENCE = "steps.ruff-version.outputs.version"


def _load_toml(path: Path) -> dict[str, Any]:
    with path.open("rb") as file:
        return cast(dict[str, Any], tomllib.load(file))


def _authoritative_version() -> str:
    dependencies = _load_toml(ROOT / "pyproject.toml")["project"]["optional-dependencies"]["dev"]
    ruff_requirements = [
        requirement for requirement in dependencies if requirement.startswith("ruff")
    ]
    if len(ruff_requirements) != 1:
        raise ValueError(f"expected one Ruff dev dependency, found {ruff_requirements!r}")

    match = re.fullmatch(r"ruff==([^;,\s]+)", ruff_requirements[0])
    if match is None:
        raise ValueError(
            "pyproject.toml must contain one exact Ruff pin in project.optional-dependencies.dev"
        )
    return match.group(1)


def _locked_version() -> str:
    packages = _load_toml(ROOT / "uv.lock")["package"]
    versions = [str(package["version"]) for package in packages if package["name"] == "ruff"]
    if len(versions) != 1:
        raise ValueError(f"expected one locked Ruff package, found {versions!r}")
    return versions[0]


def _pre_commit_version() -> str:

View on GitHub (pinned to 322425c43b)

Solutions

  1. Inspect pyproject.toml's dev group: `grep -n ruff pyproject.toml` and keep exactly one entry.
  2. Remove the duplicate, or rename the non-tool dependency so it no longer starts with `ruff`.
  3. If Ruff moved groups on purpose, update the script's lookup path to the new group.

Example fix

# pyproject.toml - before
[project.optional-dependencies]
dev = ["ruff==0.5.0", "ruff-lsp==0.1.0"]

# after
[project.optional-dependencies]
dev = ["ruff==0.5.0"]
Defensive patterns

Strategy: validation

Validate before calling

import tomllib
from pathlib import Path

def ruff_pins(pyproject: Path) -> list[str]:
    deps = tomllib.loads(pyproject.read_text())["project"]["optional-dependencies"]["dev"]
    return [d for d in deps if d.startswith("ruff")]
# assert len(ruff_pins(...)) == 1 in a pre-commit hook

Prevention

When it happens

Trigger: Adding a second Ruff-family dev dependency (any requirement string beginning with `ruff`, such as `ruff-vscode` or a duplicated pin); removing the Ruff pin entirely; moving Ruff to a different dependency group.

Common situations: Dependabot/Renovate PRs that add or duplicate Ruff entries; refactors moving dev deps into separate groups; a rename that accidentally starts with the substring `ruff`.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/75b3d76f024c1787. Report an issue: GitHub.