headroomlabs-ai/headroom · error · ValueError
expected one locked Ruff package, found {versions!r}
Error message
expected one locked Ruff package, found {versions!r} What it means
verify-ruff-version.py parses uv.lock and requires exactly one `package` entry named `ruff`. Zero entries means Ruff was dropped from the lock (stale lock after pyproject changes); multiple entries means the lock resolved two different Ruff versions, typically across conflicting environment markers or platforms.
Source
Thrown at scripts/verify-ruff-version.py:47
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:
lines = (ROOT / ".pre-commit-config.yaml").read_text(encoding="utf-8").splitlines()
for index, line in enumerate(lines):
if line.strip() != f"- repo: {RUFF_PRE_COMMIT_REPO}":
continue
for candidate in lines[index + 1 :]:
stripped = candidate.strip()
if stripped.startswith("- repo:"):
break
if stripped.startswith("rev:"):
return stripped.removeprefix("rev:").strip().removeprefix("v")
break
raise ValueError(f"could not find a rev for {RUFF_PRE_COMMIT_REPO}")
View on GitHub (pinned to 322425c43b)
Solutions
- Regenerate the lock: `uv lock` from the repo root, then rerun the verify script.
- If duplicates persist, inspect `uv lock` output for why Ruff resolves twice (conflicting markers) and unify the constraints.
- Never hand-edit uv.lock; resolve merge conflicts by regenerating.
Defensive patterns
Strategy: validation
Validate before calling
import tomllib
from pathlib import Path
def locked_ruff_versions(uv_lock: Path) -> list[str]:
packages = tomllib.loads(uv_lock.read_text())["package"]
return [str(p["version"]) for p in packages if p["name"] == "ruff"]
# assert len(locked_ruff_versions(...)) == 1 after every `uv lock` Prevention
- Regenerate uv.lock (`uv lock`) immediately after any pyproject dependency edit.
- Resolve lockfile merge conflicts by regenerating, never by manual merging.
- Include verify-ruff-version.py in CI to keep pyproject and lock in lockstep.
When it happens
Trigger: Editing pyproject.toml without running `uv lock` afterward; the lock containing ruff twice under different resolution forks (multiple platform/marker branches); a hand-edited uv.lock.
Common situations: Dependabot bumping pyproject but not regenerating uv.lock; platform-specific resolution forking after adding markers; lockfile merge conflicts resolved by keeping both entries.
Related errors
- expected one Ruff dev dependency, found {ruff_requirements!r
- sentence-transformers is required for LocalEmbedder. Install
- openai is required for OpenAIEmbedder. Install it with: pip
- httpx is required for OllamaEmbedder. Install it with: pip i
- hnswlib is required for HNSWVectorIndex. Install with: pip i
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/df5be7b0fa287b0b.
Report an issue: GitHub.