Skip to content

fix(devcontainers): restore XDG dirs so mise works under nested volume mounts - #150

Merged
gatezh merged 1 commit into
masterfrom
fix/restore-xdg-dirs
Sep 17, 2026
Merged

gatezh merged 1 commit into
masterfrom
fix/restore-xdg-dirs

Conversation

@gatezh

@gatezh gatezh commented Sep 17, 2026

Copy link
Copy Markdown
Owner

What

Restores ~/.local/share (and the other XDG dirs mise uses) to the claude-code and repo devcontainer images, fixing mise install failing in every consuming project. Also tightens comments across the Dockerfiles and renovate.json5.

Why

Regression from #146. That PR replaced

-  /home/node/.local/share/fish \
+  /home/node/.local \

in the mkdir -p list. The old path created .local/share as a side effect of mkdir -p; the new one stops a level short, so .local/share is absent from the published image:

$ docker run --rm ghcr.io/gatezh/devcontainers/claude-code:latest ls -ld /home/node/.local/share
ls: cannot access '/home/node/.local/share': No such file or directory

Consuming projects still mount a *-fish-data volume at /home/node/.local/share/fish — unchanged by the zsh migration. When Docker mounts a volume at a nested path whose parent is missing from the image, it creates that parent itself, owned by root:

drwxr-xr-x node node  /home/node/.local
drwxr-xr-x root root  /home/node/.local/share      <-- invented by Docker
drwxr-xr-x root root  /home/node/.local/share/fish

mise writes ~/.local/share/mise as uid 1000 and is refused:

mise ERROR Failed to install aqua:jqlang/jq@1.7.1: failed create_dir_all:
           ~/.local/share/mise/installs/jq/1.7.1: Permission denied (os error 13)

Running containers are unaffected only because they hold pre-#146 image IDs. Every consuming project breaks on its next rebuild. Reported by another session after a blog devcontainer's updateContentCommand exited 1.

Changes

  • claude-code and .devcontainer: create .local/share, .local/state, .config, .cache node-owned — the four dirs mise doctor reports. claude-bun is unaffected (no mise, never created them).
  • CI verify asserts .local/share is node-owned in both ci.yml and build-claude-code.yml.
  • Comments tightened: claude-code 45%→38% comment lines, renovate.json5 42%→30%. Per-package manifest lists left alone — one short line per package is the useful form.

Notes

Verification needs the nested mount. A plain docker run on the image will not reproduce this. Confirmed both directions:

.local/share mise install
published :latest + fish volume root root fails, 0 tools
this branch + fish volume node node jq@1.7.1 installed

The vestigial mount is deliberately not addressed here. Consumers still mount *-fish-data at a fish path while the image ships zsh. Renaming it requires a consumer-side migration, and the image must keep working with the mounts that exist today — which is exactly what this PR restores. Worth a separate issue.

Why it wasn't caught: the failure is invisible without a nested volume mount, so image-level CI checks and a local docker run both passed. The new assertion closes that by checking the directory exists node-owned in the image, which is what prevents Docker from inventing it.

…e mounts

24afdcf replaced mkdir -p /home/node/.local/share/fish with /home/node/.local,
dropping the .local/share level that mkdir -p had created as a side effect.
Consuming projects still mount a *-fish-data volume at ~/.local/share/fish, and
Docker invents a missing mount parent as root:root -- so .local/share became
root-owned and mise could no longer create ~/.local/share/mise. Every consuming
devcontainer fails 'mise install' on its next rebuild.

Creates .local/share, .local/state, .config and .cache node-owned (the four dirs
mise writes to) in both affected images. Reproduced against the published image
and verified with a volume mounted at the nested path -- a plain docker run does
not surface it.

CI now asserts .local/share is node-owned, since the failure is silent until a
consumer rebuilds.

Also tightens comments across the Dockerfiles and renovate.json5: claude-code
45%->38% comment lines, renovate.json5 42%->30%. Package-manifest lists are left
alone -- one short line per package is the useful form.
@gatezh
gatezh merged commit efc464f into master Sep 17, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant