headroomlabs-ai/headroom · error · OSError
binary cache directory parent is not writable: {dest.parent}
Error message
binary cache directory parent is not writable: {dest.parent} What it means
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.
Source
Thrown at headroom/binaries.py:243
def _mirror_url(url: str) -> str:
mirror = os.environ.get("HEADROOM_BINARIES_MIRROR")
if not mirror:
return url
# Only substitute the github.com host so that paths remain intact.
for prefix in ("https://github.com", "https://objects.githubusercontent.com"):
if url.startswith(prefix):
return mirror.rstrip("/") + url[len(prefix) :]
return url
# ---------- Download + verify --------------------------------------------- #
def _download(url: str, dest: Path, *, progress: bool = True) -> None:
if os.environ.get("HEADROOM_BINARIES_OFFLINE"):
raise OfflineError(f"offline mode (HEADROOM_BINARIES_OFFLINE=1) but fetch required: {url}")
if not _has_writable_existing_parent(dest.parent):
raise OSError(f"binary cache directory parent is not writable: {dest.parent}")
dest.parent.mkdir(parents=True, exist_ok=True)
if not _is_writable_dir(dest.parent):
raise OSError(f"binary cache directory is not writable: {dest.parent}")
final_url = _mirror_url(url)
req = urllib.request.Request(final_url, headers={"User-Agent": "headroom-binaries/1"})
attempts = 3
for attempt in range(1, attempts + 1):
try:
with urllib.request.urlopen(req, timeout=60) as resp: # noqa: S310 (https)
total = int(resp.headers.get("Content-Length") or 0)
_stream_to(resp, dest, total, label=dest.name, show_progress=progress)
return
except urllib.error.URLError as e:
dest.unlink(missing_ok=True)
if attempt == attempts:
raise BinaryFetchError(
f"failed to download {final_url} after {attempts} attempts: {e}"
) from eView on GitHub (pinned to 322425c43b)
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.
Example fix
# before
volumeMounts: [] # readOnlyRootFilesystem: true -> OSError
# after
volumeMounts:
- {name: bin-cache, mountPath: /opt/headroom/bin}
volumes:
- {name: bin-cache, emptyDir: {}} Defensive patterns
Strategy: validation
Validate before calling
import os
def cache_parent_writable(cache_parent: str) -> bool:
p = Path(cache_parent)
probe = p if p.exists() else next((a for a in p.parents if a.exists()), Path('/'))
return os.access(probe, os.W_OK)
if not cache_parent_writable("/opt/headroom/bin"):
raise SystemExit("cache parent not writable; set a writable cache dir or fix ownership") Try / catch
try:
ensure_binary(tool)
except OSError as e:
if "not writable" in str(e):
logger.error("fix cache dir permissions (%s) or move it to a writable path", e)
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- binary cache directory is not writable: {dest.parent}
- {tool}: distributed via PyPI only; `pip install headroom-ai`
- {tool}: no prebuilt binary for {plat.key()}; supported: {sup
- offline mode (HEADROOM_BINARIES_OFFLINE=1) but fetch require
- failed to download {final_url} after {attempts} attempts: {e
AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15).
Data as JSON: /api/errors/735e211d2daaa0f5.
Report an issue: GitHub.