feat(devcontainer): refresh dev image and auto-update Claude Code#154
feat(devcontainer): refresh dev image and auto-update Claude Code#154dpage wants to merge 1 commit into
Conversation
Updates the development container to base on Go 1.25 Trixie, add the Claude Code devcontainer feature, mount the host's gh config and Claude commands/settings into the container, forward the dev/docs/MCP ports, and pass GH_TOKEN through. postCreateCommand symlinks the mounted Claude config into ~/.claude, adds a `claude --dangerously-skip-permissions` alias, and runs `sudo env "PATH=$PATH" npm update -g @anthropic-ai/claude-code` so the update can write to the root-owned global node_modules while still locating the nvm-managed npm binary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalkthroughThis PR updates ChangesDevcontainer Configuration Update
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| ErrorProne | 1 critical |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
.devcontainer/dev/devcontainer.json (1)
27-28: 💤 Low valueStale template comments are now misleading.
The commented-out
"features": {}(line 28) suggests features aren't configured, but they are (lines 7–15). Similarly, the commented-out"postCreateCommand": "go version"(line 48) shadows the activepostCreateCommandon line 25. Both should be removed.🧹 Suggested cleanup
- // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - // Use 'forwardPorts' to make a list of ports inside the container available locally.- // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "go version", - // Configure tool-specific properties.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.devcontainer/dev/devcontainer.json around lines 27 - 28, Remove the stale commented template properties that are misleading: delete the commented-out "features": {} entry and the commented-out "postCreateCommand": "go version" so only the active "features" block (lines with the actual features) and the active postCreateCommand remain; search for the literal strings "features": {} and "postCreateCommand": "go version" in .devcontainer/dev/devcontainer.json and remove their commented instances.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.devcontainer/dev/devcontainer.json:
- Line 25: The postCreateCommand currently force-adds an alias 'alias
claude="claude --dangerously-skip-permissions"' into ~/.bashrc which silently
disables safety prompts; remove that hardcoded alias from the
"postCreateCommand" invocation and instead implement an opt-in flow: either (A)
append a visible commented snippet or instructions to ~/.bashrc (not an active
alias) that explain how to enable the flag manually, and add a postAttachCommand
that prints a clear one-time notice about the bypass and how to opt in, or (B)
document the opt-in step in README and leave no changes to ~/.bashrc; update any
references to the alias in the repo accordingly and ensure the unique string
alias claude="claude --dangerously-skip-permissions" is no longer silently
injected.
- Line 19: The devcontainer bind uses
"source=${localEnv:HOME}/.claude/settings.json,target=/home/vscode/.claude-host/settings.json,type=bind,readonly"
which will fail or create a host dir if the file is missing; update
initializeCommand to check for the existence of
${localEnv:HOME}/.claude/settings.json and either create a sensible default file
or emit a clear error and abort container start, or alternatively make the
postCreateCommand symlink conditional (only create the symlink if the target
file exists) so the container start won't fail or corrupt the host filesystem.
- Around line 21-24: The initializeCommand export of GH_TOKEN won't propagate to
containerEnv because localEnv is resolved before initializeCommand runs; fix by
having initializeCommand write the token to a file (e.g., create a .env with
GH_TOKEN=<token>) and then pass that file into the container via runArgs (e.g.,
--env-file .env) so the container receives GH_TOKEN, or alternatively remove
initializeCommand and containerEnv and rely on the existing ~/.config/gh
bind-mount for authentication; adjust the keys initializeCommand, containerEnv,
GH_TOKEN, runArgs and the .env filename accordingly.
---
Nitpick comments:
In @.devcontainer/dev/devcontainer.json:
- Around line 27-28: Remove the stale commented template properties that are
misleading: delete the commented-out "features": {} entry and the commented-out
"postCreateCommand": "go version" so only the active "features" block (lines
with the actual features) and the active postCreateCommand remain; search for
the literal strings "features": {} and "postCreateCommand": "go version" in
.devcontainer/dev/devcontainer.json and remove their commented instances.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a77f92dc-2d2a-47db-b708-150b891cf9ef
📒 Files selected for processing (1)
.devcontainer/dev/devcontainer.json
| "mounts": [ | ||
| "source=${localEnv:HOME}/.config/gh,target=/home/vscode/.config/gh,type=bind", | ||
| "source=${localEnv:HOME}/.claude/commands,target=/home/vscode/.claude-host/commands,type=bind,readonly", | ||
| "source=${localEnv:HOME}/.claude/settings.json,target=/home/vscode/.claude-host/settings.json,type=bind,readonly" |
There was a problem hiding this comment.
File bind mount will fail or corrupt host filesystem if ~/.claude/settings.json doesn't exist.
If you use --volume to bind-mount a file or directory that does not yet exist on the Docker host, Docker automatically creates the directory on the host for you. It's always created as a directory. For --mount type=bind, Docker does not automatically create the directory — instead it produces an error: invalid mount config for type "bind": bind source path does not exist.
The devcontainer string-syntax mount is treated as a --volume-style mount. A developer whose machine lacks ~/.claude/settings.json will either get a container startup error or will have that path silently created as an empty directory on their host. In the latter case, the postCreateCommand symlink points into a directory and Claude will fail to read its settings.
Add a guard in initializeCommand (or document the prerequisite):
🔧 Suggested fix: ensure the settings file exists before the container starts
- "initializeCommand": "export GH_TOKEN=$(gh auth token 2>/dev/null)",
+ "initializeCommand": "mkdir -p ~/.claude && touch ~/.claude/settings.json",Or make the symlink conditional in postCreateCommand:
-"postCreateCommand": "mkdir -p ~/.claude && ln -sf ~/.claude-host/commands ~/.claude/commands && ln -sf ~/.claude-host/settings.json ~/.claude/settings.json && echo 'alias claude=\"claude --dangerously-skip-permissions\"' >> ~/.bashrc && sudo env \"PATH=$PATH\" npm update -g `@anthropic-ai/claude-code`"
+"postCreateCommand": "mkdir -p ~/.claude && ln -sf ~/.claude-host/commands ~/.claude/commands && [ -f ~/.claude-host/settings.json ] && ln -sf ~/.claude-host/settings.json ~/.claude/settings.json || true && echo 'alias claude=\"claude --dangerously-skip-permissions\"' >> ~/.bashrc && sudo env \"PATH=$PATH\" npm update -g `@anthropic-ai/claude-code`"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.devcontainer/dev/devcontainer.json at line 19, The devcontainer bind uses
"source=${localEnv:HOME}/.claude/settings.json,target=/home/vscode/.claude-host/settings.json,type=bind,readonly"
which will fail or create a host dir if the file is missing; update
initializeCommand to check for the existence of
${localEnv:HOME}/.claude/settings.json and either create a sensible default file
or emit a clear error and abort container start, or alternatively make the
postCreateCommand symlink conditional (only create the symlink if the target
file exists) so the container start won't fail or corrupt the host filesystem.
| "initializeCommand": "export GH_TOKEN=$(gh auth token 2>/dev/null)", | ||
| "containerEnv": { | ||
| "GH_TOKEN": "${localEnv:GH_TOKEN}" | ||
| }, |
There was a problem hiding this comment.
initializeCommand export cannot inject GH_TOKEN into containerEnv.
initializeCommand runs in a subprocess; any export there does not propagate to the parent devcontainer CLI process, so ${localEnv:GH_TOKEN} is resolved from the host shell's existing environment — not from the command's output. This is a known limitation: "when building the container, the build arg is empty, but it would be expected that it inherited the exported value" — the devcontainer runtime resolves localEnv references before executing initializeCommand.
The initializeCommand line is effectively a no-op; GH_TOKEN inside the container will be empty unless the user already has it exported in the shell that launched VS Code. Since ~/.config/gh is bind-mounted (line 17), gh CLI auth still works, but any tool relying on the GH_TOKEN env var will find it unset.
🔧 Suggested fix: write the token via `initializeCommand` to a local `.env` file and reference it with `runArgs`
- "initializeCommand": "export GH_TOKEN=$(gh auth token 2>/dev/null)",
- "containerEnv": {
- "GH_TOKEN": "${localEnv:GH_TOKEN}"
- },
+ "initializeCommand": "mkdir -p .devcontainer/dev && gh auth token 2>/dev/null | { read t; [ -n \"$t\" ] && echo \"GH_TOKEN=$t\" || true; } > .devcontainer/dev/.env.local || true",
+ "runArgs": ["--env-file", ".devcontainer/dev/.env.local"],Alternatively, simply remove initializeCommand and containerEnv entirely and rely on the ~/.config/gh mount for authentication, which already provides full gh CLI access inside the container.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "initializeCommand": "export GH_TOKEN=$(gh auth token 2>/dev/null)", | |
| "containerEnv": { | |
| "GH_TOKEN": "${localEnv:GH_TOKEN}" | |
| }, | |
| "initializeCommand": "mkdir -p .devcontainer/dev && gh auth token 2>/dev/null | { read t; [ -n \"$t\" ] && echo \"GH_TOKEN=$t\" || true; } > .devcontainer/dev/.env.local || true", | |
| "runArgs": ["--env-file", ".devcontainer/dev/.env.local"], |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.devcontainer/dev/devcontainer.json around lines 21 - 24, The
initializeCommand export of GH_TOKEN won't propagate to containerEnv because
localEnv is resolved before initializeCommand runs; fix by having
initializeCommand write the token to a file (e.g., create a .env with
GH_TOKEN=<token>) and then pass that file into the container via runArgs (e.g.,
--env-file .env) so the container receives GH_TOKEN, or alternatively remove
initializeCommand and containerEnv and rely on the existing ~/.config/gh
bind-mount for authentication; adjust the keys initializeCommand, containerEnv,
GH_TOKEN, runArgs and the .env filename accordingly.
| "containerEnv": { | ||
| "GH_TOKEN": "${localEnv:GH_TOKEN}" | ||
| }, | ||
| "postCreateCommand": "mkdir -p ~/.claude && ln -sf ~/.claude-host/commands ~/.claude/commands && ln -sf ~/.claude-host/settings.json ~/.claude/settings.json && echo 'alias claude=\"claude --dangerously-skip-permissions\"' >> ~/.bashrc && sudo env \"PATH=$PATH\" npm update -g @anthropic-ai/claude-code", |
There was a problem hiding this comment.
--dangerously-skip-permissions alias bypasses all Claude Code safety prompts unconditionally.
Hardcoding this into ~/.bashrc silently overrides the flag for every terminal session. Developers who clone the repo and use this container may not realise they've opted in. Consider printing a notice to the terminal (via ~/.bashrc or postAttachCommand) so the bypass is visible, or making it an opt-in step documented in the README rather than an automatic side effect of container creation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.devcontainer/dev/devcontainer.json at line 25, The postCreateCommand
currently force-adds an alias 'alias claude="claude
--dangerously-skip-permissions"' into ~/.bashrc which silently disables safety
prompts; remove that hardcoded alias from the "postCreateCommand" invocation and
instead implement an opt-in flow: either (A) append a visible commented snippet
or instructions to ~/.bashrc (not an active alias) that explain how to enable
the flag manually, and add a postAttachCommand that prints a clear one-time
notice about the bypass and how to opt in, or (B) document the opt-in step in
README and leave no changes to ~/.bashrc; update any references to the alias in
the repo accordingly and ensure the unique string alias claude="claude
--dangerously-skip-permissions" is no longer silently injected.
Summary
mcr.microsoft.com/devcontainers/go:2-1.25-trixieand replace the Node/docker-in-docker features with thegithub-cliandclaude-codedevcontainer features.~/.config/gh,~/.claude/commands, and~/.claude/settings.jsoninto the container, passGH_TOKENthrough, and forward the Vite (5173), docs (8000), and MCP (8080) ports.postCreateCommand, symlink the mounted Claude config under~/.claude, add aclaude --dangerously-skip-permissionsalias, and auto-update@anthropic-ai/claude-codeviasudo env "PATH=$PATH" npm update -g …so the update can write to the root-owned globalnode_moduleswhile still locating the nvm-managednpm.Test plan
postCreateCommandcompletes without the priorEACCES/npm: command not founderrors.claude --versioninside the container reflects the latest published release after rebuild.gh auth statusworks inside the container (mounted gh config +GH_TOKEN).🤖 Generated with Claude Code
Summary by CodeRabbit