{"record":{"id":"735e211d2daaa0f5","repo":"headroomlabs-ai/headroom","slug":"binary-cache-directory-parent-is-not-writable-de","errorCode":null,"errorMessage":"binary cache directory parent is not writable: {dest.parent}","messagePattern":"binary cache directory parent is not writable: (.+?)","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"headroom/binaries.py","lineNumber":243,"sourceCode":"def _mirror_url(url: str) -> str:\n    mirror = os.environ.get(\"HEADROOM_BINARIES_MIRROR\")\n    if not mirror:\n        return url\n    # Only substitute the github.com host so that paths remain intact.\n    for prefix in (\"https://github.com\", \"https://objects.githubusercontent.com\"):\n        if url.startswith(prefix):\n            return mirror.rstrip(\"/\") + url[len(prefix) :]\n    return url\n\n\n# ---------- Download + verify --------------------------------------------- #\n\n\ndef _download(url: str, dest: Path, *, progress: bool = True) -> None:\n    if os.environ.get(\"HEADROOM_BINARIES_OFFLINE\"):\n        raise OfflineError(f\"offline mode (HEADROOM_BINARIES_OFFLINE=1) but fetch required: {url}\")\n    if not _has_writable_existing_parent(dest.parent):\n        raise OSError(f\"binary cache directory parent is not writable: {dest.parent}\")\n    dest.parent.mkdir(parents=True, exist_ok=True)\n    if not _is_writable_dir(dest.parent):\n        raise OSError(f\"binary cache directory is not writable: {dest.parent}\")\n    final_url = _mirror_url(url)\n    req = urllib.request.Request(final_url, headers={\"User-Agent\": \"headroom-binaries/1\"})\n    attempts = 3\n    for attempt in range(1, attempts + 1):\n        try:\n            with urllib.request.urlopen(req, timeout=60) as resp:  # noqa: S310 (https)\n                total = int(resp.headers.get(\"Content-Length\") or 0)\n                _stream_to(resp, dest, total, label=dest.name, show_progress=progress)\n            return\n        except urllib.error.URLError as e:\n            dest.unlink(missing_ok=True)\n            if attempt == attempts:\n                raise BinaryFetchError(\n                    f\"failed to download {final_url} after {attempts} attempts: {e}\"\n                ) from e","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/binaries.py#L225-L261","documentation":"Before attempting mkdir, _download checks that the nearest existing ancestor of the destination's parent is writable (_has_writable_existing_parent). A plain OSError (not a custom type) is raised naming dest.parent, because no amount of mkdir can succeed when the existing part of the path is on read-only media or owned by another user.","triggerScenarios":"Binary cache directory rooted under a path whose existing portion is not writable by the current user — e.g. /opt/headroom/bin without chown, or a read-only root filesystem in a hardened container.","commonSituations":"Containers running as non-root with the cache configured under /usr/local or another root-owned tree; read-only rootfs (securityContext.readOnlyRootFilesystem) with no writable volume mounted for the cache.","solutions":["Relocate the binary cache to a writable location (the cache root is derived from dest; set the corresponding HEADROOM cache env var, e.g. XDG-style HOME/.cache, or mount an emptyDir at the old path).","chown/chmod the existing parent so the runtime user can write: chown -R appuser /opt/headroom.","In Kubernetes with readOnlyRootFilesystem, mount a writable volume (emptyDir) at the cache path."],"exampleFix":"# before\nvolumeMounts: []  # readOnlyRootFilesystem: true -> OSError\n\n# after\nvolumeMounts:\n- {name: bin-cache, mountPath: /opt/headroom/bin}\nvolumes:\n- {name: bin-cache, emptyDir: {}}","handlingStrategy":"validation","validationCode":"import os\n\ndef cache_parent_writable(cache_parent: str) -> bool:\n    p = Path(cache_parent)\n    probe = p if p.exists() else next((a for a in p.parents if a.exists()), Path('/'))\n    return os.access(probe, os.W_OK)\n\nif not cache_parent_writable(\"/opt/headroom/bin\"):\n    raise SystemExit(\"cache parent not writable; set a writable cache dir or fix ownership\")","typeGuard":null,"tryCatchPattern":"try:\n    ensure_binary(tool)\nexcept OSError as e:\n    if \"not writable\" in str(e):\n        logger.error(\"fix cache dir permissions (%s) or move it to a writable path\", e)\n    raise","preventionTips":["Keep binary caches under the runtime user's home or a mounted writable volume.","With readOnlyRootFilesystem, always mount an emptyDir at the cache path.","Assert writability in a startup healthcheck before any download is needed."],"tags":["python","binaries","filesystem","permissions","containers"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}