Skip to content

fix(claude-code): create /etc/claude-code as root so managed-settings is readable - #101

Merged
gatezh merged 1 commit into
masterfrom
fix-managed-settings-dir-perms
May 13, 2026
Merged

gatezh merged 1 commit into
masterfrom
fix-managed-settings-dir-perms

Conversation

@gatezh

@gatezh gatezh commented May 13, 2026

Copy link
Copy Markdown
Owner

What

Pre-creates /etc/claude-code as root before COPY-ing managed-settings.json into it, and adds a test -r precondition to the CI verify-command.

Why

PR #100's image-rebuild run (25776401543) failed all four verify legs with:

jq: error: Could not open file /etc/claude-code/managed-settings.json: Permission denied

Reproduced locally against the published image at digest sha256:2b31ced4…:

$ docker run --rm ghcr.io/gatezh/devcontainers/claude-code:latest \
    ls -ld /etc/claude-code
drw-r--r-- 2 root root 4096 May 13 03:28 /etc/claude-code

Mode 0644 on a directory means readable but not traversable — the file inside is unreachable by any user, including root inside this container.

Root cause: BuildKit applies COPY --chmod=N not only to the file but to any parent directories it auto-creates. The previous COPY --chmod=0644 managed-settings.json /etc/claude-code/managed-settings.json created /etc/claude-code with mode 0644 (no x bit) as a side effect.

The file mode itself was correct; the bug was relying on BuildKit to materialize the parent dir with a sane default.

Changes

  • claude-code/.devcontainer/Dockerfile — switch to USER root, mkdir -p /etc/claude-code (inherits root's umask → 0755), COPY --chown=root:root --chmod=0644 managed-settings.json …, then drop back to USER node. Adds the file under standard /etc/ ownership conventions.
  • .github/workflows/build-claude-code.yml — prefix the four verify-command rows with test -r /etc/claude-code/managed-settings.json so the next time this directory perm class of bug appears, CI fails with an explicit "file not readable" before jq is invoked. Same printenv | grep -qx quote-free shape as the surrounding checks.

Notes

  • Verified locally — for real this time. Built --target base from the modified Dockerfile, confirmed /etc/claude-code is now drwxr-xr-x and managed-settings.json is -rw-r--r--, ran the exact verify-command under bash -c "<value>" (matching the GHA matrix expansion) inside the container — exit 0.
  • Lesson logged: my "local verification" on PRs fix(claude-code): patch playwright MCP on every session start (#98) #99 and fix(ci): verify managed-settings.json without runner-shell quote stripping #100 was insufficient because I only validated the predicate against a copy of the JSON file at a path I controlled. The published image's actual perms were never checked. From now on, image-changing PRs need a docker build --target <stage> + in-container repro before push, not just a syntactic check of the diff.

Test plan

  • hadolint clean on modified Dockerfile.
  • actionlint clean on modified workflow.
  • Locally built image: /etc/claude-code mode 0755, file mode 0644, readable as node.
  • Exact verify-command under bash -c "<value>" exits 0 in the locally built image.
  • CI Build claude-code post-merge run on master — all four verify legs (default/sandbox × amd64/arm64) should pass.

Recovers from #99 / #100. Closes the loop on #98.

… is readable

BuildKit applies COPY --chmod to any parent directories it auto-creates,
so "COPY --chmod=0644 managed-settings.json /etc/claude-code/managed-settings.json"
landed the file inside a drw-r--r-- directory — no execute bit, not traversable
by anyone in the container. The node user could not read the file even though
the file mode itself was correct, and jq in the CI verify step failed with
"Could not open file ... Permission denied".

Pre-create the directory explicitly as root before the COPY, and copy the file
with --chown=root:root so it matches normal /etc/ ownership conventions.

Also add a test -r precondition to the verify-command so future breakage of
this kind surfaces with a clear "file unreadable" failure before jq sees it.

Verified locally: built --target base from the modified Dockerfile, confirmed
/etc/claude-code is now drwxr-xr-x and managed-settings.json is -rw-r--r--
readable as the node user, and the full verify-command exits 0 under the
exact bash -c wrapping the CI matrix uses.
@gatezh
gatezh merged commit 6f01d09 into master May 13, 2026
8 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