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.
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 asroot:root, and the container user loses~/.cacheentirely.Measured (2026-08-16, docker 29.7.2)
Stock
mcr.microsoft.com/devcontainers/base:ubuntu, run as uid 1000 (vscode):~/.cacheownermkdir ~/.cache/pip-test10000(root)The pixi mount itself stays writable, so
pixi global syncis 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:~/.cachedevcontainers/base:ubuntu,base:ubuntu-24.04,rust:latestwayfinder-devcontainer(built),devlaunch-main(built)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_DIRpointing 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: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/nodeat 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
0777does rescue the cross-uid case in a simple test, but it is not a rigorous fix: package subdirectories are created0755owned 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/dotfilesinstall.sh:44-48chowns-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-133reaches it by a second path, guarded on.configwritability but chowning.cachetoo.)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 preflightexists()check on the mount source (a typo'd source currently failsuploudly, then a retriedsshsilently 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.