{"record":{"id":"df5be7b0fa287b0b","repo":"headroomlabs-ai/headroom","slug":"expected-one-locked-ruff-package-found-versions","errorCode":null,"errorMessage":"expected one locked Ruff package, found {versions!r}","messagePattern":"expected one locked Ruff package, found (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scripts/verify-ruff-version.py","lineNumber":47,"sourceCode":"    ruff_requirements = [\n        requirement for requirement in dependencies if requirement.startswith(\"ruff\")\n    ]\n    if len(ruff_requirements) != 1:\n        raise ValueError(f\"expected one Ruff dev dependency, found {ruff_requirements!r}\")\n\n    match = re.fullmatch(r\"ruff==([^;,\\s]+)\", ruff_requirements[0])\n    if match is None:\n        raise ValueError(\n            \"pyproject.toml must contain one exact Ruff pin in project.optional-dependencies.dev\"\n        )\n    return match.group(1)\n\n\ndef _locked_version() -> str:\n    packages = _load_toml(ROOT / \"uv.lock\")[\"package\"]\n    versions = [str(package[\"version\"]) for package in packages if package[\"name\"] == \"ruff\"]\n    if len(versions) != 1:\n        raise ValueError(f\"expected one locked Ruff package, found {versions!r}\")\n    return versions[0]\n\n\ndef _pre_commit_version() -> str:\n    lines = (ROOT / \".pre-commit-config.yaml\").read_text(encoding=\"utf-8\").splitlines()\n    for index, line in enumerate(lines):\n        if line.strip() != f\"- repo: {RUFF_PRE_COMMIT_REPO}\":\n            continue\n        for candidate in lines[index + 1 :]:\n            stripped = candidate.strip()\n            if stripped.startswith(\"- repo:\"):\n                break\n            if stripped.startswith(\"rev:\"):\n                return stripped.removeprefix(\"rev:\").strip().removeprefix(\"v\")\n        break\n    raise ValueError(f\"could not find a rev for {RUFF_PRE_COMMIT_REPO}\")\n\n","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/scripts/verify-ruff-version.py#L29-L65","documentation":"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.","triggerScenarios":"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.","commonSituations":"Dependabot bumping pyproject but not regenerating uv.lock; platform-specific resolution forking after adding markers; lockfile merge conflicts resolved by keeping both entries.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import tomllib\nfrom pathlib import Path\n\ndef locked_ruff_versions(uv_lock: Path) -> list[str]:\n    packages = tomllib.loads(uv_lock.read_text())[\"package\"]\n    return [str(p[\"version\"]) for p in packages if p[\"name\"] == \"ruff\"]\n# assert len(locked_ruff_versions(...)) == 1 after every `uv lock`","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["uv","lockfile","ruff","dependencies"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}