{"record":{"id":"e73234980b919034","repo":"headroomlabs-ai/headroom","slug":"binary-cache-directory-is-not-writable-dest-pare","errorCode":null,"errorMessage":"binary cache directory is not writable: {dest.parent}","messagePattern":"binary cache directory is not writable: (.+?)","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"headroom/binaries.py","lineNumber":246,"sourceCode":"        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\n            # GitHub release assets occasionally return a transient 5xx or\n            # reset while redirecting to the object store. A short bounded\n            # retry keeps proxy startup reliable without hiding persistent","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/binaries.py#L228-L264","documentation":"After mkdir(parents=True, exist_ok=True) succeeds, _download re-checks with _is_writable_dir that the resulting directory actually accepts writes. This catches cases mkdir cannot: directories that exist but are root-owned with no write bit, filesystems mounted read-only after the fact, or permission changes racing the check. Raised as plain OSError naming dest.parent.","triggerScenarios":"Cache parent exists and is listable (so the parent check passed) but lacks write permission for the current user — mode 755 root-owned dir with the process running as non-root; or the directory sits on a read-only mount.","commonSituations":"Docker volumes mounted from the host with root ownership, Kubernetes hostPath volumes, or shared cache dirs shared across users with restrictive umask.","solutions":["chmod/chown the cache directory for the runtime user: chown appuser <dir> && chmod u+w <dir>.","Point the cache at a user-writable path (HOME/.cache/...) instead of a shared/system location.","For read-only mounts, remount read-write or move the cache elsewhere."],"exampleFix":"# before\n$ ls -ld /opt/headroom/bin\n# drwxr-xr-x root root -> OSError\n\n# after\nsudo chown -R appuser: /opt/headroom/bin","handlingStrategy":"validation","validationCode":"import os, tempfile\n\ndef dir_accepts_writes(d: str) -> bool:\n    try:\n        with tempfile.TemporaryFile(dir=d):\n            return True\n    except OSError:\n        return False\n\nif not dir_accepts_writes(cache_dir):\n    raise SystemExit(f\"{cache_dir} exists but is not writable for this user\")","typeGuard":null,"tryCatchPattern":"try:\n    ensure_binary(tool)\nexcept OSError as e:\n    if \"not writable\" in str(e):\n        subprocess.run([\"chown\", \"-R\", getpass.getuser(), cache_dir])  # or alert ops\n        ensure_binary(tool)  # one targeted retry after fixing perms\n    else:\n        raise","preventionTips":["Set explicit ownership/modes on cache dirs in Dockerfiles (chown appuser).","Avoid root-owned hostPath/Docker volumes for caches used by non-root services.","Watch for read-only mounts (NFS, ro volumes) when choosing a cache location."],"tags":["python","binaries","filesystem","permissions"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}