Skip to content

Shared pixi cache mount makes ~/.cache root-owned in any image that lacks it, and hard-fails on a uid mismatch #240

Description

@blooop

The shared pixi cache mount (#232, PR #236) targets /home/vscode/.cache/devlaunch-pixi. When the image has no /home/vscode/.cache — which the stock devcontainer images do not — Docker creates the missing parent as root:root, and the container user loses ~/.cache entirely.

Measured (2026-08-16, docker 29.7.2)

Stock mcr.microsoft.com/devcontainers/base:ubuntu, run as uid 1000 (vscode):

~/.cache owner mkdir ~/.cache/pip-test
no mount (control) 1000 OK
with the mount as shipped 0 (root) Permission denied

The pixi mount itself stays writable, so pixi global sync is fine — everything else that writes ~/.cache (pip, uv, pre-commit, fontconfig…) silently breaks.

Which images are hit — it depends only on whether the image already ships ~/.cache:

image ships ~/.cache with mount
devcontainers/base:ubuntu, base:ubuntu-24.04, rust:latest no BROKEN
wayfinder-devcontainer (built), devlaunch-main (built) yes (uid 1000) writable

So our own built images are unaffected, and every stock third-party devcontainer is. Mount args are appended unconditionally (dl.py:3656), so no dotfiles are involved; it applies to newly created containers only, since mounts are fixed at create time.

Second, related failure: cross-uid writes

PIXI_CACHE_DIR pointing at a directory the container user cannot write is a hard failure, not a degradation — pixi does not fall back to reading. Measured with a warm cache already containing the package:

× failed to open '/shared/repodata/....shards-cache-v1'
╰─▶ Permission denied (os error 13)
Error: × Couldn't install the following environments: jq     (exit 1)

Control: the same uid with its own writable cache installs fine, so the cause is the cache's ownership, not the uid. Sharing therefore requires container uid == host uid, or root. Every mainstream base declares remoteUser = vscode/node at uid 1000, and this host is uid 1000, so the common case is safe; an image with any other uid fails to provision tools at all.

Making the host dir 0777 does rescue the cross-uid case in a simple test, but it is not a rigorous fix: package subdirectories are created 0755 owned by whichever uid wrote them, so two uids can still block each other on a shared package or repodata file.

Third: the dotfiles chown amplifier

blooop/dotfiles install.sh:44-48 chowns -R "$USER:$USER" "$HOME/.cache" when it finds it root-owned — and the mount manufactures exactly that precondition. Verified write-through: a container at uid 2000 chowned the host directory 1000 → 2000, after which the host user could not write its own cache. A second container at uid 1000 chowns it back — the two ping-pong on every launch. (install.sh:131-133 reaches it by a second path, guarded on .config writability but chowning .cache too.)

Done when

The mount target moves out of $HOME — e.g. /var/cache/devlaunch/pixi, whose parent exists in every image — which fixes all three at once: no root-owned ~/.cache, no dependence on the container's home path or username, and out of reach of the dotfiles chown. Pin it with a test asserting the target is not under any home directory, and add a preflight exists() check on the mount source (a typo'd source currently fails up loudly, then a retried ssh silently starts a container without the mount). Decide and record what happens when the container uid cannot write the cache — at minimum it should not be a hard provisioning failure.

This ticket was generated by AI during investigation follow-through; all measurements above were taken live on 2026-08-16.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions