dev-base: non-root agents can't install their repo's required Python (root-owned UV_PYTHON_INSTALL_DIR) #327

Closed
opened 2026-07-05 02:10:40 +00:00 by coilysiren · 4 comments
Owner

Problem

A non-root agent in a dev-base container cannot install the Python its repo requires, so uv run dies and every headless run on that repo fails its test/smoke gate before it can land.

docker/dev-base/Dockerfile env-sets UV_PYTHON_INSTALL_DIR=/opt/uv/python (line ~38) and later runs chmod -R a+rX /opt/uv (line ~68). That grants read and traverse, not write. No managed Python is pre-installed, and the base image ships only system python3 (3.12 on bookworm, line ~47). So when a repo requires a newer Python (for example eco-app's requires-python = ">=3.13", product image FROM python:3.13), uv must download 3.13 into /opt/uv/python, which the non-root agent cannot create:

error: failed to create directory `/opt/uv/python`: Permission denied (os error 13)

Impact

Every warded engineer run on any repo whose required Python is not the system 3.12 dies on its first uv run (test, lint, smoke). It surfaced on coilyco-gaming/eco-app: three dispatched runs left zero commits and zero branches, killed silently at the smoke gate. Local workaround filed as coilyco-gaming/eco-app#56 (redirect UV_PYTHON_INSTALL_DIR to a HOME-writable dir in that repo's Makefile), but that is a per-repo bandage - the fleet needs the image fixed.

Fix options (pick one or both)

  1. Pre-install the fleet's Pythons at build (as root, so ownership is fine): uv python install 3.13 3.12 before the chmod -R a+rX /opt/uv. Consumers then find 3.13 already present, no runtime download, offline-safe. Add versions as the fleet adopts them.
  2. Make the install dir agent-writable: create /opt/uv/python and grant group/world write (or chown to the agent uid), so uv can download any version on demand at run time.

Option 1 is the more deterministic fix (no network at run time). Option 2 is the flexible fallback for versions not pre-baked. Doing both is reasonable: pre-bake the common versions and leave the dir writable for the rest.

Acceptance

  • In a fresh dev-base container as a non-root uid, uv run --python 3.13 python -V succeeds with no writable-path workaround.
  • The image rebuild flows through the existing publish-image path on push to main.

Origin

Diagnosed from the coilyco-gaming/eco-app read-only director session, 2026-07-05. Motivating incident and local workaround: coilyco-gaming/eco-app#56. This is the root-cause fix that #56 bandages, and the reason CI-in-pinned-dev-base parity is worth adopting fleet-wide (companion umbrella issue).

## Problem A non-root agent in a dev-base container cannot install the Python its repo requires, so `uv run` dies and every headless run on that repo fails its test/smoke gate before it can land. `docker/dev-base/Dockerfile` env-sets `UV_PYTHON_INSTALL_DIR=/opt/uv/python` (line ~38) and later runs `chmod -R a+rX /opt/uv` (line ~68). That grants read and traverse, **not write**. No managed Python is pre-installed, and the base image ships only system `python3` (3.12 on bookworm, line ~47). So when a repo requires a newer Python (for example eco-app's `requires-python = ">=3.13"`, product image `FROM python:3.13`), uv must download 3.13 into `/opt/uv/python`, which the non-root agent cannot create: ``` error: failed to create directory `/opt/uv/python`: Permission denied (os error 13) ``` ## Impact Every `warded` engineer run on any repo whose required Python is not the system 3.12 dies on its first `uv run` (test, lint, smoke). It surfaced on coilyco-gaming/eco-app: three dispatched runs left zero commits and zero branches, killed silently at the smoke gate. Local workaround filed as coilyco-gaming/eco-app#56 (redirect `UV_PYTHON_INSTALL_DIR` to a HOME-writable dir in that repo's Makefile), but that is a per-repo bandage - the fleet needs the image fixed. ## Fix options (pick one or both) 1. **Pre-install the fleet's Pythons at build** (as root, so ownership is fine): `uv python install 3.13 3.12` before the `chmod -R a+rX /opt/uv`. Consumers then find 3.13 already present, no runtime download, offline-safe. Add versions as the fleet adopts them. 2. **Make the install dir agent-writable**: create `/opt/uv/python` and grant group/world write (or chown to the agent uid), so uv can download any version on demand at run time. Option 1 is the more deterministic fix (no network at run time). Option 2 is the flexible fallback for versions not pre-baked. Doing both is reasonable: pre-bake the common versions and leave the dir writable for the rest. ## Acceptance * In a fresh dev-base container as a non-root uid, `uv run --python 3.13 python -V` succeeds with no writable-path workaround. * The image rebuild flows through the existing publish-image path on push to main. ## Origin Diagnosed from the coilyco-gaming/eco-app read-only director session, 2026-07-05. Motivating incident and local workaround: coilyco-gaming/eco-app#56. This is the root-cause fix that #56 bandages, and the reason CI-in-pinned-dev-base parity is worth adopting fleet-wide (companion umbrella issue).
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-agentic-os-327 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-05T02:13:16Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-agentic-os-327` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-05T02:13:16Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

🔓 Reservation released by ward container reap — container agentic-os (--driver claude) exited without launching the agent (smoke-test death, ward#222/#264), so the hold it took is retracted. A plain ward agent retry no longer needs --force.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation-released --> 🔓 Reservation released by `ward container reap` — container `agentic-os` (`--driver claude`) exited without launching the agent (smoke-test death, ward#222/#264), so the hold it took is retracted. A plain `ward agent` retry no longer needs `--force`. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-agentic-os-327 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-05T02:35:52Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-agentic-os-327` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-05T02:35:52Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - dev-base now pre-installs Python 3.13 + 3.12 and leaves UV_PYTHON_INSTALL_DIR world-writable, so non-root agents can uv run without the Permission-denied wall.

Straightforward root-cause fix once I traced it: the chmod -R a+rX /opt/uv really did grant read+traverse but never write, and nothing pre-seeded a managed interpreter. I took both options the issue offered - pre-bake the common versions as root for the offline/deterministic path, and an a+rwX on the install dir for on-demand downloads of anything not baked. The one ordering gotcha was making sure the a+rwX runs after the pre-commit block's a+rX so the write bit isn't a no-op.

No real fights, but two rough edges. First, I couldn't build the image here (no docker daemon in the container), so I validated the mechanism directly with the same uv 0.11.26: uv python install 3.13 3.12 lands both under UV_PYTHON_INSTALL_DIR and uv run --python 3.13/3.12 resolves the pre-baked interpreter with zero download - the interpreter layer #327 is about. Full end-to-end proof waits on the publish-image rebuild. Second, the docs cap fought back: the managed-Pythons bullet tipped dev-base-image.md past the 4000-char documentation-layout cap, so I had to tighten several neighboring bullets to fit rather than split the doc.

Fairly confident in the result. Follow-up worth considering: fold the version list into dep-bump so new fleet Pythons ride the auto-bump instead of a hand edit, and eco-app#56's per-repo Makefile bandage can now be reverted.

WARD-OUTCOME: done - dev-base now pre-installs Python 3.13 + 3.12 and leaves UV_PYTHON_INSTALL_DIR world-writable, so non-root agents can `uv run` without the Permission-denied wall. Straightforward root-cause fix once I traced it: the `chmod -R a+rX /opt/uv` really did grant read+traverse but never write, and nothing pre-seeded a managed interpreter. I took both options the issue offered - pre-bake the common versions as root for the offline/deterministic path, and an `a+rwX` on the install dir for on-demand downloads of anything not baked. The one ordering gotcha was making sure the `a+rwX` runs *after* the pre-commit block's `a+rX` so the write bit isn't a no-op. No real fights, but two rough edges. First, I couldn't build the image here (no docker daemon in the container), so I validated the mechanism directly with the same uv 0.11.26: `uv python install 3.13 3.12` lands both under UV_PYTHON_INSTALL_DIR and `uv run --python 3.13/3.12` resolves the pre-baked interpreter with zero download - the interpreter layer #327 is about. Full end-to-end proof waits on the publish-image rebuild. Second, the docs cap fought back: the managed-Pythons bullet tipped dev-base-image.md past the 4000-char documentation-layout cap, so I had to tighten several neighboring bullets to fit rather than split the doc. Fairly confident in the result. Follow-up worth considering: fold the version list into dep-bump so new fleet Pythons ride the auto-bump instead of a hand edit, and eco-app#56's per-repo Makefile bandage can now be reverted.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-flight-deck/agentic-os#327
No description provided.