Skip to content

feat(devcontainer): refresh dev image and auto-update Claude Code#154

Open
dpage wants to merge 1 commit into
mainfrom
feat/devcontainer-claude-code-auto-update
Open

feat(devcontainer): refresh dev image and auto-update Claude Code#154
dpage wants to merge 1 commit into
mainfrom
feat/devcontainer-claude-code-auto-update

Conversation

@dpage
Copy link
Copy Markdown
Member

@dpage dpage commented May 7, 2026

Summary

  • Rebase the dev container on mcr.microsoft.com/devcontainers/go:2-1.25-trixie and replace the Node/docker-in-docker features with the github-cli and claude-code devcontainer features.
  • Bind-mount the host's ~/.config/gh, ~/.claude/commands, and ~/.claude/settings.json into the container, pass GH_TOKEN through, and forward the Vite (5173), docs (8000), and MCP (8080) ports.
  • On postCreateCommand, symlink the mounted Claude config under ~/.claude, add a claude --dangerously-skip-permissions alias, and auto-update @anthropic-ai/claude-code via sudo env "PATH=$PATH" npm update -g … so the update can write to the root-owned global node_modules while still locating the nvm-managed npm.

Test plan

  • Rebuild the dev container from a clean state and confirm postCreateCommand completes without the prior EACCES / npm: command not found errors.
  • Verify claude --version inside the container reflects the latest published release after rebuild.
  • Confirm gh auth status works inside the container (mounted gh config + GH_TOKEN).
  • Confirm Vite (5173), docs (8000), and MCP (8080) port-forward labels appear in the Ports panel.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Updated development environment configuration with enhanced GitHub CLI and Claude Code integrations.
    • Refined port forwarding settings to improve development server connectivity.

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>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

Walkthrough

This PR updates .devcontainer/dev/devcontainer.json to integrate GitHub CLI and Claude Code tooling into the development container. It replaces the Node and Docker-in-Docker features, adds host file bind mounts for token and configuration persistence, configures GitHub token environment variables, and defines explicit port forwarding for development services with auto-forward notifications.

Changes

Devcontainer Configuration Update

Layer / File(s) Summary
Container Features
.devcontainer/dev/devcontainer.json
Replaces node and docker-in-docker features with github-cli (latest) and claude-code (latest) for GitHub API and Claude command-line tool integration.
File Mounts and Persistence
.devcontainer/dev/devcontainer.json
Adds bind mounts for ~/.config/gh, ~/.claude, and ~/.local/bin/claude from host into /home/vscode container paths to persist authentication and configuration.
Environment and Initialization
.devcontainer/dev/devcontainer.json
Adds initializeCommand to export GH_TOKEN, sets containerEnv.GH_TOKEN from local environment, and expands postCreateCommand to link Claude files, append bash alias, and globally update @anthropic-ai/claude-code npm package.
Port Forwarding
.devcontainer/dev/devcontainer.json
Replaces implicit port configuration with explicit forwardPorts (5173, 8000, 8080) and portsAttributes providing descriptive labels and auto-forward notification behavior.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

  • pgEdge/pgedge-postgres-mcp#92: Both PRs modify .devcontainer/dev/devcontainer.json, changing base image, enabled features, and postCreate command behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: refreshing the dev image to Go 1.25-trixie and adding Claude Code auto-update in postCreateCommand.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/devcontainer-claude-code-auto-update

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented May 7, 2026

Not up to standards ⛔

🔴 Issues 1 critical

Alerts:
⚠ 1 issue (≤ 0 issues of at least critical severity)

Results:
1 new issue

Category Results
ErrorProne 1 critical

View in Codacy

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
.devcontainer/dev/devcontainer.json (1)

27-28: 💤 Low value

Stale 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 active postCreateCommand on 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

📥 Commits

Reviewing files that changed from the base of the PR and between bfbef42 and 79085b1.

📒 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"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Comment on lines +21 to +24
"initializeCommand": "export GH_TOKEN=$(gh auth token 2>/dev/null)",
"containerEnv": {
"GH_TOKEN": "${localEnv:GH_TOKEN}"
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
"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",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

--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.

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