{"record":{"id":"131dec44a9802028","repo":"headroomlabs-ai/headroom","slug":"unknown-tool-tool-r-known-sorted-tools","errorCode":null,"errorMessage":"unknown tool {tool!r}; known: {sorted(tools)}","messagePattern":"unknown tool (.+?); known: (.+?)","errorType":"exception","errorClass":"KeyError","httpStatus":null,"severity":"error","filePath":"headroom/binaries.py","lineNumber":198,"sourceCode":"\n# ---------- Registry ------------------------------------------------------ #\n\n\n_REGISTRY_PATH = Path(__file__).parent / \"tools.json\"\n\n\n@functools.lru_cache(maxsize=1)\ndef _registry() -> dict[str, Any]:\n    with _REGISTRY_PATH.open(\"r\", encoding=\"utf-8\") as f:\n        data: dict[str, Any] = json.load(f)\n    return data\n\n\ndef _tool_entry(tool: str) -> dict[str, Any]:\n    reg = _registry()\n    tools: dict[str, Any] = reg.get(\"tools\", {})\n    if tool not in tools:\n        raise KeyError(f\"unknown tool {tool!r}; known: {sorted(tools)}\")\n    entry: dict[str, Any] = tools[tool]\n    return entry\n\n\ndef _is_pypi_tool(tool: str) -> bool:\n    entry = _tool_entry(tool)\n    return entry.get(\"version\") == \"pypi\" or not entry.get(\"assets\")\n\n\ndef _asset_for_platform(tool: str, plat: PlatformKey) -> dict[str, Any]:\n    entry = _tool_entry(tool)\n    if _is_pypi_tool(tool):\n        raise PlatformNotSupported(\n            f\"{tool}: distributed via PyPI only; `pip install headroom-ai` \"\n            f\"should have placed `{entry.get('binary', tool)}` on PATH.\"\n        )\n    assets: dict[str, Any] = entry.get(\"assets\", {})\n    asset: dict[str, Any] | None = assets.get(plat.key())","sourceCodeStart":180,"sourceCodeEnd":216,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/binaries.py#L180-L216","documentation":"_tool_entry raises KeyError when the requested tool name is not a key in the 'tools' object of the bundled registry JSON (loaded once via lru_cache from _REGISTRY_PATH). The message lists the exact sorted set of known tool names, so a typo is immediately visible.","triggerScenarios":"Calling any registry-backed helper (_is_pypi_tool, _asset_for_platform, and the public ensure/install APIs) with a misspelled or version-removed tool name, e.g. 'difftastic' instead of 'difft'.","commonSituations":"Typos, passing an executable path instead of the registry tool name, or referencing a tool that exists only in a newer/older headroom-ai release's registry than the installed one.","solutions":["Read the 'known:' list in the error message and use one of those exact names.","If the tool should exist, upgrade (or pin) headroom-ai to the release whose registry contains it.","Inspect the registry directly: python -c \"from headroom.binaries import _registry; print(sorted(_registry()['tools']))\".","If adding a new tool, add its entry to the registry JSON instead of calling with an unregistered name."],"exampleFix":"# before\nensure_binary(\"difftastic\")\n\n# after\nensure_binary(\"difft\")  # exact name from the registry's known list","handlingStrategy":"validation","validationCode":"from headroom.binaries import _registry\n\ndef known_tools() -> set[str]:\n    return set(_registry()[\"tools\"])\n\ndef is_known_tool(name: str) -> bool:\n    return name in known_tools()\n\nassert is_known_tool(\"difft\"), f\"pick from {sorted(known_tools())}\"","typeGuard":"def is_known_tool(tool: object) -> bool:\n    return isinstance(tool, str) and tool in _registry().get(\"tools\", {})","tryCatchPattern":"try:\n    entry = _tool_entry(tool)\nexcept KeyError as e:\n    logger.error(\"tool not in registry: %s; known: %s\", tool, sorted(_registry()[\"tools\"]))\n    raise","preventionTips":["Validate tool names against the registry once at config load time.","Freeze tool lists in integration tests so a registry change fails CI instead of production.","Print the known list in CLI help text derived from the registry itself."],"tags":["python","registry","tooling","key-error"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}