chore: integrate Claude Code into devcontainer for autonomus development#3166
chore: integrate Claude Code into devcontainer for autonomus development#3166
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRedesigns the devcontainer to a build-based setup: expanded Dockerfile with architecture-aware tooling, new host and container init scripts for Git worktrees and Claude config, a post-create bootstrap (permissions, Claude merge), a strict outbound firewall script, plus README and env additions; CI prebuild workflow and prior build JSON removed. Changes
Sequence Diagram(s)sequenceDiagram
rect rgba(200,200,255,0.5)
participant Dev as "Developer Host"
participant InitHost as "init-host.sh"
participant Git as "Host .git / Worktree"
end
rect rgba(200,255,200,0.5)
participant Build as "VS Code / devcontainer build"
participant Container as "Dev Container"
participant Post as "post-create.sh"
participant Firewall as "init-firewall.sh"
end
rect rgba(255,200,200,0.5)
participant Claude as "Anthropic/Claude Config"
participant External as "External Services (GitHub, Registry, Crates, APIs)"
end
Dev->>InitHost: run init-host.sh (stage .claude, resolve .git)
InitHost->>Git: read/resolve worktree or main .git
InitHost-->>Dev: write .devcontainer/.claude-host-config & .main-git-resolved
Dev->>Build: open devcontainer (build with Dockerfile)
Build->>Container: create image with toolchain, mounts, env
Container->>Post: run post-create.sh
Post->>Claude: copy/merge staged Claude config into /home/vscode/.claude
Post->>Git: create symlink to resolved host .git, mark workspace safe, set permissions
Container->>Firewall: run init-firewall.sh
Firewall->>External: resolve domains, fetch GitHub CIDRs, populate ipset
Firewall->>External: allow only resolved IPs/CIDRs (DNS, SSH, required registries)
Container-->>Dev: ready message (requires ANTHROPIC_API_KEY on host)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (6)
.devcontainer/init-firewall.sh (2)
92-92: Consider using conntrack instead of deprecated state module.The
--stateoption is deprecated in favor of-m conntrack --ctstate. This is a minor compatibility note for newer iptables versions.Suggested update
-iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT +iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/init-firewall.sh at line 92, Replace the deprecated state match in the iptables rule: update the rule that currently uses "-m state --state ESTABLISHED,RELATED -j ACCEPT" (found in the init-firewall script) to use the conntrack match instead by switching to "-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT" so it uses the modern conntrack module while preserving the same behavior.
62-68: Consider validating GitHub CIDR ranges before adding to iptables.The GitHub meta API response is used directly in iptables rules without validation. A compromised or malformed response could inject unexpected rules. While the
2>/dev/null || trueon line 112 suppresses errors, explicit validation would be safer.Suggested validation
# GitHub (dynamic IP ranges - added as CIDR rules below since ipset doesn't support /16 etc.) -GITHUB_IPS=$(curl -s https://api.github.com/meta 2>/dev/null | jq -r '.web[], .api[], .git[], .actions[]' 2>/dev/null || true) +GITHUB_IPS=$(curl -sf --max-time 10 https://api.github.com/meta 2>/dev/null | jq -r '.web[], .api[], .git[], .actions[]' 2>/dev/null | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?$' || true)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/init-firewall.sh around lines 62 - 68, The script currently assigns GITHUB_IPS from the GitHub meta API and feeds those values into firewall rules; instead validate each entry before applying rules: after GITHUB_IPS is populated, iterate its items and verify each is a valid IPv4/IPv6 address or CIDR (use a strict regex or syscalls like ipcalc/ip in the shell) and reject anything that doesn't match, log rejected/malformed entries, and only pass sanitized CIDR strings to resolve_and_allow or the iptables commands; implement this validation where GITHUB_IPS is set and before any use of resolve_and_allow/iptables so resolve_and_allow and iptables only ever receive trusted, validated CIDR/IP strings..devcontainer/post-create.sh (1)
44-49: Redundant dotfile copy loop — same issue as in init-host.sh.The
cp -a "$HOST_CONFIG"/. "$CLAUDE_DIR"/on line 44 already copies hidden files. The subsequent loop duplicates this.Suggested simplification
if [ -d "$HOST_CONFIG" ] && [ "$(ls -A "$HOST_CONFIG" 2>/dev/null)" ]; then echo "Copying Claude config from host..." cp -a "$HOST_CONFIG"/. "$CLAUDE_DIR"/ 2>/dev/null || true - for item in "$HOST_CONFIG"/.*; do - basename="$(basename "$item")" - [ "$basename" = "." ] || [ "$basename" = ".." ] && continue - cp -a "$item" "$CLAUDE_DIR/$basename" 2>/dev/null || true - done chmod 600 "$CLAUDE_DIR/.credentials.json" 2>/dev/null || true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/post-create.sh around lines 44 - 49, The duplicate copy is caused by the initial cp -a "$HOST_CONFIG"/. "$CLAUDE_DIR"/ which already copies hidden files, followed by the for loop (for item in "$HOST_CONFIG"/.*) that re-copies dotfiles; remove the entire loop (the for ... do ... done block) and keep the single cp -a "$HOST_CONFIG"/. "$CLAUDE_DIR"/ command so hidden files are copied once and behavior matches the init-host.sh approach..devcontainer/init-host.sh (1)
14-22: Redundant dotfile copy loop —cp -a dir/.already copies hidden files.The command on line 16 (
cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/) already copies all contents including dotfiles. The loop on lines 18-22 duplicates this work.Suggested simplification
if [ -d "$HOME/.claude" ] && [ "$(ls -A "$HOME/.claude" 2>/dev/null)" ]; then mkdir -p "$CLAUDE_STAGING" cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/ 2>/dev/null || true - # Also copy dotfiles - for item in "$HOME/.claude"/.*; do - base="$(basename "$item")" - [ "$base" = "." ] || [ "$base" = ".." ] && continue - cp -a "$item" "$CLAUDE_STAGING/$base" 2>/dev/null || true - done # Also copy ~/.claude.json (onboarding state, outside ~/.claude/) [ -f "$HOME/.claude.json" ] && cp -a "$HOME/.claude.json" "$CLAUDE_STAGING/.claude.json.root" 2>/dev/null || true fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/init-host.sh around lines 14 - 22, The dotfile copy loop is redundant because the existing cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/ already copies hidden files; remove the entire for-loop that iterates over "$HOME/.claude"/.* (the block that computes base and cp -a "$item" "$CLAUDE_STAGING/$base") and keep the mkdir -p "$CLAUDE_STAGING" and the single cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/ 2>/dev/null || true call so CLAUDE_STAGING population and existing error suppression remain intact..devcontainer/Dockerfile (2)
1-2: Consider pinning the base image version for reproducibility.The
:ubuntutag will pull whatever the latest Ubuntu-based devcontainer image is at build time. For more reproducible builds, consider pinning to a specific version (e.g.,:ubuntu-24.04or using a digest).That said, for a devcontainer where staying current may be desirable, this is an acceptable trade-off.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/Dockerfile around lines 1 - 2, The Dockerfile uses an unpinned base image "FROM mcr.microsoft.com/devcontainers/base:ubuntu" which makes builds non-reproducible; update that FROM line to reference a specific, pinned tag or digest (for example "mcr.microsoft.com/devcontainers/base:ubuntu-24.04" or a sha256 digest) so builds are deterministic, and document the chosen pin in the Dockerfile comment; locate the FROM instruction in the Dockerfile and replace the ":ubuntu" tag with the chosen version or digest.
64-71: TOML parsing is fragile and may break with different formatting.The current parsing assumes
channel = "version"with spaces around=. TOML allowschannel="version"(no spaces), which would causeawk '{print $3}'to fail.Consider a more robust extraction:
♻️ More robust TOML parsing
COPY --chown=vscode:vscode rust-toolchain.toml /tmp/rust-toolchain.toml -RUN TOOLCHAIN_VERSION="$(grep channel /tmp/rust-toolchain.toml | awk '{print $3}' | tr -d '"')" && \ +RUN TOOLCHAIN_VERSION="$(grep -oP 'channel\s*=\s*"\K[^"]+' /tmp/rust-toolchain.toml)" && \ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- \Alternatively, using
sed:TOOLCHAIN_VERSION="$(sed -n 's/^channel[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' /tmp/rust-toolchain.toml)"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/Dockerfile around lines 64 - 71, The current RUN that sets TOOLCHAIN_VERSION from rust-toolchain.toml using grep/awk is fragile (it depends on spacing); change the extraction used in the RUN command to a robust regex-based parser (e.g., sed) that captures the value of channel regardless of spaces or quoting, so TOOLCHAIN_VERSION is correctly set from rust-toolchain.toml before invoking the rustup installer; keep the rest of the RUN flow (invoking sh.rustup.rs with --default-toolchain and --target wasm32-unknown-unknown and removing /tmp/rust-toolchain.toml) unchanged, and reference the variables and files exactly as used: TOOLCHAIN_VERSION, rust-toolchain.toml, and the existing RUN invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.devcontainer/.claude-config-resolved:
- Line 1: Remove the machine-specific file .devcontainer/.claude-config-resolved
from the repository and stop tracking it: add an appropriate ignore pattern
(e.g., .devcontainer/.claude-config-resolved or a broader
.devcontainer/*.resolved) to .gitignore, remove the tracked file from git (git
rm --cached or git rm as appropriate), and commit the change so the personal
path (/Users/ivanshumkov/.claude) is not stored in the repo; ensure the
.devcontainer/.claude-config-resolved entry is present in .gitignore to prevent
future commits of that generated, user-specific file.
In @.devcontainer/README.md:
- Around line 108-114: The README statement saying the host's `~/.claude/`
directory is "mounted read-only" is inaccurate because the scripts init-host.sh
and post-create.sh actually copy the config into a staging area and then into a
persistent Docker volume; update the text in .devcontainer/README.md to say the
config is copied into a persistent Docker volume (not mounted), and keep/clarify
the notes that post-create.sh forces bypassPermissions and skips the safety
confirmation prompt and that host-specific path references are copied as-is and
may log harmless warnings in-container.
---
Nitpick comments:
In @.devcontainer/Dockerfile:
- Around line 1-2: The Dockerfile uses an unpinned base image "FROM
mcr.microsoft.com/devcontainers/base:ubuntu" which makes builds
non-reproducible; update that FROM line to reference a specific, pinned tag or
digest (for example "mcr.microsoft.com/devcontainers/base:ubuntu-24.04" or a
sha256 digest) so builds are deterministic, and document the chosen pin in the
Dockerfile comment; locate the FROM instruction in the Dockerfile and replace
the ":ubuntu" tag with the chosen version or digest.
- Around line 64-71: The current RUN that sets TOOLCHAIN_VERSION from
rust-toolchain.toml using grep/awk is fragile (it depends on spacing); change
the extraction used in the RUN command to a robust regex-based parser (e.g.,
sed) that captures the value of channel regardless of spaces or quoting, so
TOOLCHAIN_VERSION is correctly set from rust-toolchain.toml before invoking the
rustup installer; keep the rest of the RUN flow (invoking sh.rustup.rs with
--default-toolchain and --target wasm32-unknown-unknown and removing
/tmp/rust-toolchain.toml) unchanged, and reference the variables and files
exactly as used: TOOLCHAIN_VERSION, rust-toolchain.toml, and the existing RUN
invocation.
In @.devcontainer/init-firewall.sh:
- Line 92: Replace the deprecated state match in the iptables rule: update the
rule that currently uses "-m state --state ESTABLISHED,RELATED -j ACCEPT" (found
in the init-firewall script) to use the conntrack match instead by switching to
"-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT" so it uses the modern
conntrack module while preserving the same behavior.
- Around line 62-68: The script currently assigns GITHUB_IPS from the GitHub
meta API and feeds those values into firewall rules; instead validate each entry
before applying rules: after GITHUB_IPS is populated, iterate its items and
verify each is a valid IPv4/IPv6 address or CIDR (use a strict regex or syscalls
like ipcalc/ip in the shell) and reject anything that doesn't match, log
rejected/malformed entries, and only pass sanitized CIDR strings to
resolve_and_allow or the iptables commands; implement this validation where
GITHUB_IPS is set and before any use of resolve_and_allow/iptables so
resolve_and_allow and iptables only ever receive trusted, validated CIDR/IP
strings.
In @.devcontainer/init-host.sh:
- Around line 14-22: The dotfile copy loop is redundant because the existing cp
-a "$HOME/.claude"/. "$CLAUDE_STAGING"/ already copies hidden files; remove the
entire for-loop that iterates over "$HOME/.claude"/.* (the block that computes
base and cp -a "$item" "$CLAUDE_STAGING/$base") and keep the mkdir -p
"$CLAUDE_STAGING" and the single cp -a "$HOME/.claude"/. "$CLAUDE_STAGING"/
2>/dev/null || true call so CLAUDE_STAGING population and existing error
suppression remain intact.
In @.devcontainer/post-create.sh:
- Around line 44-49: The duplicate copy is caused by the initial cp -a
"$HOST_CONFIG"/. "$CLAUDE_DIR"/ which already copies hidden files, followed by
the for loop (for item in "$HOST_CONFIG"/.*) that re-copies dotfiles; remove the
entire loop (the for ... do ... done block) and keep the single cp -a
"$HOST_CONFIG"/. "$CLAUDE_DIR"/ command so hidden files are copied once and
behavior matches the init-host.sh approach.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
.devcontainer/.claude-config-resolved.devcontainer/Dockerfile.devcontainer/README.md.devcontainer/devcontainer.json.devcontainer/init-firewall.sh.devcontainer/init-host.sh.devcontainer/post-create.sh.gitignoreREADME.md
| @@ -0,0 +1 @@ | |||
| /Users/ivanshumkov/.claude No newline at end of file | |||
There was a problem hiding this comment.
This file should not be committed — it contains a machine-specific path and exposes a developer's username.
This file contains a hardcoded personal path (/Users/ivanshumkov/.claude) which:
- Exposes personal information (username)
- Is machine-specific and won't work for other developers
- Appears to be generated during the init process
This file should be removed from the commit and added to .gitignore:
Proposed .gitignore addition
# Devcontainer host-resolved symlink (machine-specific)
.devcontainer/.main-git-resolved
.devcontainer/.claude-host-config
+.devcontainer/.claude-config-resolvedThen remove this file from the repository:
git rm .devcontainer/.claude-config-resolved🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.devcontainer/.claude-config-resolved at line 1, Remove the machine-specific
file .devcontainer/.claude-config-resolved from the repository and stop tracking
it: add an appropriate ignore pattern (e.g.,
.devcontainer/.claude-config-resolved or a broader .devcontainer/*.resolved) to
.gitignore, remove the tracked file from git (git rm --cached or git rm as
appropriate), and commit the change so the personal path
(/Users/ivanshumkov/.claude) is not stored in the repo; ensure the
.devcontainer/.claude-config-resolved entry is present in .gitignore to prevent
future commits of that generated, user-specific file.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.devcontainer/devcontainer.json (1)
74-77: Consider pinning the Git feature version for reproducible builds (Line 75).Using
"latest"for the version can cause non-deterministic environment drift across rebuilds. While supported by the feature (default is"os-provided"), pinning to a specific version ensures consistent environments and allows intentional updates.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/devcontainer.json around lines 74 - 77, The devcontainer feature entry "ghcr.io/devcontainers/features/git:1" currently sets "version": "latest", which can cause non-deterministic builds; update the "version" property for that feature to a fixed, explicit tag (e.g., a specific semver or release tag) instead of "latest" — change the value of the "version" key under "ghcr.io/devcontainers/features/git:1" to a pinned version string so rebuilds are reproducible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.devcontainer/devcontainer.json:
- Around line 74-77: The devcontainer feature entry
"ghcr.io/devcontainers/features/git:1" currently sets "version": "latest", which
can cause non-deterministic builds; update the "version" property for that
feature to a fixed, explicit tag (e.g., a specific semver or release tag)
instead of "latest" — change the value of the "version" key under
"ghcr.io/devcontainers/features/git:1" to a pinned version string so rebuilds
are reproducible.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.devcontainer/devcontainer-build.json.devcontainer/devcontainer.json.github/workflows/prebuild-devcontainers.yml
💤 Files with no reviewable changes (2)
- .devcontainer/devcontainer-build.json
- .github/workflows/prebuild-devcontainers.yml
…iner Copy enabledPlugins from host settings.json automatically (just IDs, no secrets). Add .env/.env.example config for users to list specific agents and skills to copy from ~/.claude/. Harden init-host.sh to only stage credentials and explicitly listed extras instead of copying the entire ~/.claude/ directory. Remove unused bash history volume. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…h option Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add CLAUDE_CODE_OAUTH_TOKEN to containerEnv so it's automatically forwarded from the host environment, same as ANTHROPIC_API_KEY. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
.devcontainer/init-host.sh (1)
13-22:⚠️ Potential issue | 🟠 MajorMove Claude auth staging out of the workspace tree.
.devcontainer/devcontainer.jsonLines 3-10 setbuild.contextto.., so this staging directory sits inside the Docker build context. Docker uses.dockerignore—not.gitignore—to exclude paths from that context, and the legacy builder sends the full context to the daemon. Keeping host auth files under.devcontainer/is still the wrong secret boundary even if the Dockerfile never copies them. Please stage these files outside the workspace and mount that host-only path explicitly instead. (containers.dev)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/init-host.sh around lines 13 - 22, The staging directory for Claude auth is currently set to CLAUDE_STAGING=".devcontainer/.claude-host-config" which places secrets inside the workspace/Docker build context; change CLAUDE_STAGING to a host-only path outside the repo (for example under "$HOME/.claude-host-config" or another directory outside the repository root), update the mkdir/cp logic to create and copy credentials into that new path (references: CLAUDE_STAGING variable, the cp lines that copy "$HOME/.claude/.credentials.json" and "$HOME/.claude.json"), and ensure whatever devcontainer/docker config mounts that host-only path into the container instead of relying on files under .devcontainer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.devcontainer/init-host.sh:
- Around line 61-78: The script can leave a stale directory at RESOLVED so
subsequent ln -sfn calls create a symlink inside that directory instead of
replacing it; update the branches that call ln -sfn (the ones using MAIN_GIT and
"$(pwd)/.git") to remove any existing $RESOLVED first (e.g. test for existence
and rm -rf "$RESOLVED") before creating the symlink, and preserve the mkdir -p
"$RESOLVED" fallback branch for the no-git case so the mount still has a
directory when needed.
---
Duplicate comments:
In @.devcontainer/init-host.sh:
- Around line 13-22: The staging directory for Claude auth is currently set to
CLAUDE_STAGING=".devcontainer/.claude-host-config" which places secrets inside
the workspace/Docker build context; change CLAUDE_STAGING to a host-only path
outside the repo (for example under "$HOME/.claude-host-config" or another
directory outside the repository root), update the mkdir/cp logic to create and
copy credentials into that new path (references: CLAUDE_STAGING variable, the cp
lines that copy "$HOME/.claude/.credentials.json" and "$HOME/.claude.json"), and
ensure whatever devcontainer/docker config mounts that host-only path into the
container instead of relying on files under .devcontainer.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 60159fb0-0b2b-478d-bbe6-de1856d474a4
📒 Files selected for processing (8)
.devcontainer/.env.example.devcontainer/.gitignore.devcontainer/README.md.devcontainer/devcontainer.json.devcontainer/init-host.sh.devcontainer/post-create.sh.gitignoreREADME.md
✅ Files skipped from review due to trivial changes (1)
- .devcontainer/.env.example
🚧 Files skipped from review as they are similar to previous changes (4)
- .devcontainer/post-create.sh
- README.md
- .devcontainer/README.md
- .gitignore
.devcontainer/init-host.sh
Outdated
| cp -a "$HOME/.claude.json" "$CLAUDE_STAGING/.claude.json.root" 2>/dev/null || true | ||
|
|
||
| # Plugins: always copy (just IDs, no secrets) | ||
| if [ -f "$HOME/.claude/settings.json" ]; then |
There was a problem hiding this comment.
Not sure you want plugin-dev plugin inside the devcontainer. It's polluting context. I'd prefer using dedicated $PROJECT/.claude/settings.json instead.
It's minor issue. You can ignore.
| done | ||
| fi | ||
|
|
||
| if [ -n "$CLAUDE_SKILLS" ]; then |
There was a problem hiding this comment.
as above.
It's minor issue.
…dd GH_TOKEN - Remove .claude-config-resolved from git (contained personal path) and add it to .gitignore to prevent future commits - Fix ln -sfn symlink bug: rm -rf $RESOLVED before ln -s to avoid creating symlink inside stale directory on subsequent runs - Pass GH_TOKEN/GITHUB_TOKEN from host env so gh CLI works in container Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (3)
.devcontainer/init-host.sh (2)
25-28:jqdependency on host machine may not be satisfied.The script uses
jqto extractenabledPluginsfromsettings.json, butjqmay not be installed on all host machines. The2>/dev/null || truefallback handles failures gracefully, but the feature silently fails without feedback.Consider either:
- Adding a check/warning when
jqis unavailable- Documenting
jqas a host prerequisite in the READMEOptional: Add availability check
# Plugins: always copy (just IDs, no secrets) if [ -f "$HOME/.claude/settings.json" ]; then + if command -v jq >/dev/null 2>&1; then jq '{enabledPlugins: .enabledPlugins}' "$HOME/.claude/settings.json" \ > "$CLAUDE_STAGING/enabled-plugins.json" 2>/dev/null || true + else + echo "Note: 'jq' not found on host; skipping plugin sync" + fi fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/init-host.sh around lines 25 - 28, The script uses jq to extract enabledPlugins but silently ignores failures; add an availability check and warning before using jq: test for jq (e.g., "command -v jq" or "which jq") and if missing emit a clear stderr warning (using echo or logger) informing the user that jq is required to populate $CLAUDE_STAGING/enabled-plugins.json and that the feature will be skipped, then proceed without running jq; update the .devcontainer/init-host.sh logic around the jq invocation (referencing "$HOME/.claude/settings.json", jq, and "$CLAUDE_STAGING/enabled-plugins.json") to perform the check and warning, or alternatively add a README prerequisite entry documenting jq as required for host setup.
33-34: Add ShellCheck directive for the dynamic source.ShellCheck reports SC1090 because it cannot statically analyze the sourced file. While the code is correct (the path is known at runtime), adding a directive improves maintainability and silences the warning in CI.
Proposed fix
ENV_FILE=".devcontainer/.env" + # shellcheck source=/dev/null [ -f "$ENV_FILE" ] && source "$ENV_FILE"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/init-host.sh around lines 33 - 34, Add a ShellCheck directive to silence SC1090 for the dynamic source: before the line that defines ENV_FILE or immediately above the source command (the line that reads [ -f "$ENV_FILE" ] && source "$ENV_FILE"), add a comment like "# shellcheck source=.devcontainer/.env" so ShellCheck knows the intended file; keep the ENV_FILE variable and the conditional source logic unchanged..devcontainer/devcontainer.json (1)
82-82: Community feature with version 0 may be unstable.The Starship feature
ghcr.io/schlich/devcontainer-features/starship:0uses major version 0, which typically indicates pre-1.0 software that may introduce breaking changes. This is a community-maintained feature rather than an official devcontainer feature.Consider pinning to a specific minor version if stability is important, or document that this is optional/cosmetic.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.devcontainer/devcontainer.json at line 82, The devcontainer is referencing a community Starship feature pinned to major version 0 ("ghcr.io/schlich/devcontainer-features/starship:0"), which may be unstable; update the devcontainer.json entry to pin a specific stable minor/patch tag (e.g., replace ":0" with a specific semver like ":0.7.5" or the latest non-0.x tag if available) or add a comment/documentation line next to the "ghcr.io/schlich/devcontainer-features/starship:0" entry to mark it as optional/cosmetic so consumers know it's not guaranteed stable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.devcontainer/devcontainer.json:
- Line 82: The devcontainer is referencing a community Starship feature pinned
to major version 0 ("ghcr.io/schlich/devcontainer-features/starship:0"), which
may be unstable; update the devcontainer.json entry to pin a specific stable
minor/patch tag (e.g., replace ":0" with a specific semver like ":0.7.5" or the
latest non-0.x tag if available) or add a comment/documentation line next to the
"ghcr.io/schlich/devcontainer-features/starship:0" entry to mark it as
optional/cosmetic so consumers know it's not guaranteed stable.
In @.devcontainer/init-host.sh:
- Around line 25-28: The script uses jq to extract enabledPlugins but silently
ignores failures; add an availability check and warning before using jq: test
for jq (e.g., "command -v jq" or "which jq") and if missing emit a clear stderr
warning (using echo or logger) informing the user that jq is required to
populate $CLAUDE_STAGING/enabled-plugins.json and that the feature will be
skipped, then proceed without running jq; update the .devcontainer/init-host.sh
logic around the jq invocation (referencing "$HOME/.claude/settings.json", jq,
and "$CLAUDE_STAGING/enabled-plugins.json") to perform the check and warning, or
alternatively add a README prerequisite entry documenting jq as required for
host setup.
- Around line 33-34: Add a ShellCheck directive to silence SC1090 for the
dynamic source: before the line that defines ENV_FILE or immediately above the
source command (the line that reads [ -f "$ENV_FILE" ] && source "$ENV_FILE"),
add a comment like "# shellcheck source=.devcontainer/.env" so ShellCheck knows
the intended file; keep the ENV_FILE variable and the conditional source logic
unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6f313d63-1b60-4b9c-b10c-403269a3305d
📒 Files selected for processing (4)
.devcontainer/.gitignore.devcontainer/devcontainer.json.devcontainer/init-host.sh.gitignore
~/.claude.json contains sensitive data: email, account UUID, session IDs, project paths with token usage stats, and feature flag assignments. It should not be copied into the devcontainer where Claude runs in bypass mode. Authentication already works via .credentials.json alone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lugin sync - Remove automatic syncing of all host enabledPlugins — plugins should be defined per-project in the repo's .claude/settings.json - Keep opt-in CLAUDE_AGENTS and CLAUDE_SKILLS in .env for users who want to bring specific personal agents/skills into the container - Credentials (.credentials.json) are still always copied from the host Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.devcontainer/init-host.sh:
- Around line 29-48: Don't source ENV_FILE on the host; instead treat
CLAUDE_AGENTS and CLAUDE_SKILLS as input strings only and validate each name
before using it—remove the "source $ENV_FILE" behavior and ensure parsing uses
CLAUDE_AGENTS/CLAUDE_SKILLS as already-exported env vars; for each item in
AGENT_LIST/SKILL_LIST trim whitespace, reject any name containing "/" or ".."
(and ideally allow only a safe charset like [A-Za-z0-9._-]), and derive the file
path using a safe basename-like step when constructing src (the variables
AGENT_LIST, SKILL_LIST, src, and the cp usage) so path-traversal is impossible;
if a name fails validation, skip it and log or ignore rather than copying.
In @.devcontainer/post-create.sh:
- Around line 56-72: The script currently overwrites SETTINGS_FILE
("$CLAUDE_DIR/settings.json") with a minimal SETTINGS payload, losing any
existing user settings; change it to read the existing settings.json if present
and use jq to merge/override only the required keys
("permissions.defaultMode":"bypassPermissions" and
"skipDangerousModePermissionPrompt":true) into the existing JSON (or into an
empty object when missing), then write the merged result back to SETTINGS_FILE
(replacing the current cat > usage). Keep the subsequent merge of
HOST_CONFIG/enabled-plugins.json as-is but operate on the merged SETTINGS_FILE
so enabled-plugins and all other preexisting settings are preserved; continue to
use TMP and atomic mv for safe writes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6bb34e39-9545-40ae-84a4-e25dda7814f9
📒 Files selected for processing (2)
.devcontainer/init-host.sh.devcontainer/post-create.sh
.devcontainer/post-create.sh
Outdated
| # Write a clean settings.json with bypassPermissions (no host settings leak) | ||
| SETTINGS_FILE="$CLAUDE_DIR/settings.json" | ||
| cat > "$SETTINGS_FILE" <<'SETTINGS' | ||
| { | ||
| "permissions": { | ||
| "defaultMode": "bypassPermissions" | ||
| }, | ||
| "skipDangerousModePermissionPrompt": true | ||
| } | ||
| SETTINGS | ||
|
|
||
| # Merge host's enabledPlugins into settings (plugin IDs only, no secrets) | ||
| if [ -f "$HOST_CONFIG/enabled-plugins.json" ]; then | ||
| TMP=$(mktemp) | ||
| jq -s '.[0] * .[1]' "$SETTINGS_FILE" "$HOST_CONFIG/enabled-plugins.json" \ | ||
| > "$TMP" 2>/dev/null && mv "$TMP" "$SETTINGS_FILE" || true | ||
| fi |
There was a problem hiding this comment.
Preserve existing Claude settings when forcing bypassPermissions.
Lines 58-65 recreate settings.json from scratch, and Lines 68-72 only merge enabledPlugins back. Any Claude settings already stored in the persistent volume are lost the next time this script runs. Merge the required permission flags into the existing file instead of replacing it.
♻️ Proposed fix
# Write a clean settings.json with bypassPermissions (no host settings leak)
SETTINGS_FILE="$CLAUDE_DIR/settings.json"
-cat > "$SETTINGS_FILE" <<'SETTINGS'
-{
- "permissions": {
- "defaultMode": "bypassPermissions"
- },
- "skipDangerousModePermissionPrompt": true
-}
-SETTINGS
+BASE_SETTINGS=$(mktemp)
+if [ -f "$SETTINGS_FILE" ]; then
+ cp -a "$SETTINGS_FILE" "$BASE_SETTINGS"
+else
+ printf '{}\n' > "$BASE_SETTINGS"
+fi
+
+TMP=$(mktemp)
+jq '.permissions.defaultMode = "bypassPermissions"
+ | .skipDangerousModePermissionPrompt = true' \
+ "$BASE_SETTINGS" > "$TMP" && mv "$TMP" "$SETTINGS_FILE"
+rm -f "$BASE_SETTINGS"
# Merge host's enabledPlugins into settings (plugin IDs only, no secrets)
if [ -f "$HOST_CONFIG/enabled-plugins.json" ]; then
TMP=$(mktemp)
jq -s '.[0] * .[1]' "$SETTINGS_FILE" "$HOST_CONFIG/enabled-plugins.json" \
> "$TMP" 2>/dev/null && mv "$TMP" "$SETTINGS_FILE" || true
fi📝 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.
| # Write a clean settings.json with bypassPermissions (no host settings leak) | |
| SETTINGS_FILE="$CLAUDE_DIR/settings.json" | |
| cat > "$SETTINGS_FILE" <<'SETTINGS' | |
| { | |
| "permissions": { | |
| "defaultMode": "bypassPermissions" | |
| }, | |
| "skipDangerousModePermissionPrompt": true | |
| } | |
| SETTINGS | |
| # Merge host's enabledPlugins into settings (plugin IDs only, no secrets) | |
| if [ -f "$HOST_CONFIG/enabled-plugins.json" ]; then | |
| TMP=$(mktemp) | |
| jq -s '.[0] * .[1]' "$SETTINGS_FILE" "$HOST_CONFIG/enabled-plugins.json" \ | |
| > "$TMP" 2>/dev/null && mv "$TMP" "$SETTINGS_FILE" || true | |
| fi | |
| # Write a clean settings.json with bypassPermissions (no host settings leak) | |
| SETTINGS_FILE="$CLAUDE_DIR/settings.json" | |
| BASE_SETTINGS=$(mktemp) | |
| if [ -f "$SETTINGS_FILE" ]; then | |
| cp -a "$SETTINGS_FILE" "$BASE_SETTINGS" | |
| else | |
| printf '{}\n' > "$BASE_SETTINGS" | |
| fi | |
| TMP=$(mktemp) | |
| jq '.permissions.defaultMode = "bypassPermissions" | |
| | .skipDangerousModePermissionPrompt = true' \ | |
| "$BASE_SETTINGS" > "$TMP" && mv "$TMP" "$SETTINGS_FILE" | |
| rm -f "$BASE_SETTINGS" | |
| # Merge host's enabledPlugins into settings (plugin IDs only, no secrets) | |
| if [ -f "$HOST_CONFIG/enabled-plugins.json" ]; then | |
| TMP=$(mktemp) | |
| jq -s '.[0] * .[1]' "$SETTINGS_FILE" "$HOST_CONFIG/enabled-plugins.json" \ | |
| > "$TMP" 2>/dev/null && mv "$TMP" "$SETTINGS_FILE" || true | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.devcontainer/post-create.sh around lines 56 - 72, The script currently
overwrites SETTINGS_FILE ("$CLAUDE_DIR/settings.json") with a minimal SETTINGS
payload, losing any existing user settings; change it to read the existing
settings.json if present and use jq to merge/override only the required keys
("permissions.defaultMode":"bypassPermissions" and
"skipDangerousModePermissionPrompt":true) into the existing JSON (or into an
empty object when missing), then write the merged result back to SETTINGS_FILE
(replacing the current cat > usage). Keep the subsequent merge of
HOST_CONFIG/enabled-plugins.json as-is but operate on the merged SETTINGS_FILE
so enabled-plugins and all other preexisting settings are preserved; continue to
use TMP and atomic mv for safe writes.
…nfig model - Plugins are no longer auto-synced from host; document settings.local.json approach instead (gitignored by Claude Code, no unknown field risk) - Agents/skills remain opt-in via CLAUDE_AGENTS/CLAUDE_SKILLS in .env - Update security model section and .env.example accordingly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| RUN if [[ "$TARGETARCH" == "arm64" ]] ; then export PROTOC_ARCH=aarch_64; else export PROTOC_ARCH=x86_64; fi; \ | ||
| curl -Ls https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip \ | ||
| RUN if [ "$TARGETARCH" = "arm64" ]; then export PROTOC_ARCH=aarch_64; else export PROTOC_ARCH=x86_64; fi; \ | ||
| curl -Ls "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip" \ |
There was a problem hiding this comment.
Maybe just call scripts/setup-ai-agent-environment.sh ?
| GITHUB_IPS=$(curl -s https://api.github.com/meta 2>/dev/null | jq -r '.web[], .api[], .git[], .actions[]' 2>/dev/null || true) | ||
| resolve_and_allow "github.com" | ||
| resolve_and_allow "api.github.com" | ||
| resolve_and_allow "raw.githubusercontent.com" |
There was a problem hiding this comment.
Effectively, it's wildcard for all the open source code in the world ;-).
| iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT | ||
|
|
||
| # Allow SSH | ||
| iptables -A OUTPUT -p tcp --dport 22 -j ACCEPT |
There was a problem hiding this comment.
why ssh? for github? Will it work if the devcontainer doesn't have ssh key?
For GH_TOKEN / GITHUB_TOKEN to work, I had to use https, like:
run: git config --global url."https://github.com/".insteadOf "git@github.com:"
There is also gh auth setup-git
Not sure you need these, just mentioning.
| fi | ||
|
|
||
| # Write a clean settings.json with bypassPermissions | ||
| cat > "$CLAUDE_DIR/settings.json" <<'SETTINGS' |
There was a problem hiding this comment.
FYI, there is settings.local.json or sth like that.
| - **Agents/skills** are only copied if explicitly listed in `.devcontainer/.env` — nothing personal leaks in by default. | ||
| - **A clean `settings.json`** is generated inside the container with `bypassPermissions` — your host's permission allowlists, MCP server configs, and hooks are not copied. | ||
| - **No shell history** is persisted or shared with the container. | ||
| - **The `.git` directory** is mounted read-write (required for commits/pushes). This is the main trust boundary — Claude can push code. |
There was a problem hiding this comment.
You might find this useful: https://github.com/lklimek/claudius/blob/main/hooks/block-github-writes.sh
lklimek
left a comment
There was a problem hiding this comment.
Code Review — 6 MEDIUM findings
3 specialist agents (security, project consistency, shell/Docker quality) reviewed 12 files, 699 additions, 169 deletions. After deduplication: 0 CRITICAL, 0 HIGH, 6 MEDIUM, 30 LOW, 5 INFO findings.
Overall this is well-architected — the container-as-sandbox model for bypassPermissions is sound, credential handling is careful, and the security model is transparently documented. The 5 inline comments below cover the actionable MEDIUM findings tied to specific code locations.
PROJ-002 (MEDIUM): PR description inaccuracy
The PR description states:
Host
~/.claude/config (credentials, skills, plugins) is staged byinit-host.sh
But init-host.sh explicitly does not copy plugins. The README correctly states "Plugins are not copied from your host." The PR description should say "credentials, and optionally agents/skills" to match the actual implementation.
What's done well
- No secrets in Docker layers — all sensitive data at runtime only
- Transparent security model — README identifies
.gitas the trust boundary - Version consistency — wasm-bindgen-cli 0.2.108 matches across Dockerfile, README, and Cargo.lock
- Git worktree support — elegant two-script host/container path resolution
- Opt-in personal config — agents/skills require explicit
.envlisting
Also noted (typo)
PR title: "autonomus" → "autonomous"
Full report (41 findings): available on request.
🤖 Co-authored by Claudius the Magnificent AI Agent
| CLAUDE_AGENTS="" | ||
| CLAUDE_SKILLS="" | ||
| ENV_FILE=".devcontainer/.env" | ||
| [ -f "$ENV_FILE" ] && source "$ENV_FILE" |
There was a problem hiding this comment.
SEC-001 (MEDIUM): source executes arbitrary shell code, not just variable assignments. This script runs on the host machine (via initializeCommand) before the container exists — the container sandbox provides zero protection here.
If .devcontainer/.env is malformed or tampered with (compromised workspace, social engineering), this enables full code execution with the developer's host privileges. Three review agents flagged this independently.
Suggestion: Replace with a safe key-value parser:
if [ -f "$ENV_FILE" ]; then
while IFS='=' read -r key value; do
case "$key" in
CLAUDE_AGENTS) CLAUDE_AGENTS="$value" ;;
CLAUDE_SKILLS) CLAUDE_SKILLS="$value" ;;
esac
done < <(grep -E '^(CLAUDE_AGENTS|CLAUDE_SKILLS)=' "$ENV_FILE")
fiThis only extracts the two expected variables and ignores everything else.
🤖 Claudius the Magnificent AI Code Review
| CLAUDE_STAGING=".devcontainer/.claude-host-config" | ||
| rm -rf "$CLAUDE_STAGING" | ||
| if [ -d "$HOME/.claude" ]; then | ||
| mkdir -p "$CLAUDE_STAGING" | ||
| # Credentials (OAuth tokens) — required for authentication | ||
| [ -f "$HOME/.claude/.credentials.json" ] && \ | ||
| cp -a "$HOME/.claude/.credentials.json" "$CLAUDE_STAGING/.credentials.json" 2>/dev/null || true |
There was a problem hiding this comment.
SEC-002 (MEDIUM): OAuth credentials staged here persist in the workspace if container creation fails before post-create.sh runs cleanup (line 80). Neither script has an EXIT/ERR trap for cleanup.
The .gitignore prevents accidental commits, but stale credentials in the workspace can be picked up by file search tools, backups, or other processes.
Suggestion: Add a trap at the top of this script and also in post-create.sh:
# init-host.sh — after CLAUDE_STAGING is set:
trap 'rm -rf "$CLAUDE_STAGING"' ERR
# post-create.sh — near the top, after HOST_CONFIG is set:
trap 'rm -rf "$HOST_CONFIG"' EXITAlso restrict permissions immediately after copy:
cp -a "$HOME/.claude/.credentials.json" "$CLAUDE_STAGING/.credentials.json" 2>/dev/null || true
chmod 600 "$CLAUDE_STAGING/.credentials.json" 2>/dev/null || true🤖 Claudius the Magnificent AI Code Review
| ```jsonc | ||
| "runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"], | ||
| "postStartCommand": "sudo /usr/local/bin/init-firewall.sh", | ||
| "waitFor": "postStartCommand" | ||
| ``` | ||
|
|
||
| You'll also need to add `iptables ipset iproute2 dnsutils` to the `apt-get install` in the Dockerfile and uncomment the firewall COPY/sudoers block. See `init-firewall.sh` for the domain whitelist. |
There was a problem hiding this comment.
PROJ-001 (MEDIUM): These instructions reference "uncomment the firewall COPY/sudoers block" but no such block exists in the current Dockerfile. A developer following this will hit a dead end.
Suggestion: Either:
- Add the commented-out COPY and sudoers lines to the Dockerfile so developers can uncomment them, or
- Rewrite the instructions to be fully self-contained — explain exactly which lines to add and where:
You'll also need to:
1. Add `iptables ipset iproute2 dnsutils` to the `apt-get install` in the Dockerfile
2. Add `COPY init-firewall.sh /usr/local/bin/init-firewall.sh` to the Dockerfile
3. Add a sudoers entry: `RUN echo "vscode ALL=(root) NOPASSWD: /usr/local/bin/init-firewall.sh" > /etc/sudoers.d/firewall`🤖 Claudius the Magnificent AI Code Review
| "source": "${localWorkspaceFolder}/.devcontainer/.main-git-resolved", | ||
| "target": "/workspace/.host-main-git", | ||
| "type": "bind" | ||
| } |
There was a problem hiding this comment.
PROJ-007 (MEDIUM): This bind mount requires .main-git-resolved to exist on the host, which is created by init-host.sh (the initializeCommand). If a developer uses a tool that doesn't support initializeCommand (raw docker build, some CI systems, GitHub Codespaces), this mount fails with an opaque Docker error like invalid mount config.
Suggestion:
- Document in the README that
initializeCommandsupport is required (VS Code Dev Containers and thedevcontainerCLI both support it; Codespaces may not). - Consider adding a manual fallback instruction for non-standard clients:
# If your tool doesn't support initializeCommand, run manually first: bash .devcontainer/init-host.sh
🤖 Claudius the Magnificent AI Code Review
| # Persist bash/zsh history | ||
| RUN mkdir -p /commandhistory && \ | ||
| touch /commandhistory/.bash_history /commandhistory/.zsh_history && \ | ||
| chown -R vscode:vscode /commandhistory |
There was a problem hiding this comment.
PROJ-008 (MEDIUM): This creates /commandhistory with bash/zsh history files, but devcontainer.json has no corresponding volume mount for this directory. Without a persistent volume, shell history is lost on every container rebuild — making these lines dead code.
The README also states "No shell history is persisted or shared with the container" (Security Model section), which contradicts this Dockerfile setup.
Suggestion: Either:
-
Add a history volume in
devcontainer.jsonand configureHISTFILE:{ "source": "devcontainer-platform-shell-history-${devcontainerId}", "target": "/commandhistory", "type": "volume" }Then set
ENV HISTFILE=/commandhistory/.bash_historyin the Dockerfile and update the README accordingly. -
Or remove these 4 lines if history persistence is intentionally not desired, keeping the README accurate.
🤖 Claudius the Magnificent AI Code Review

Issue being fixed or feature implemented
Integrate Claude Code into the Dev Container to enable autonomous AI-assisted
development with full sandbox isolation. This gives developers (and Claude itself) a
ready-to-go environment where Claude Code can build, test, and iterate on Dash Platform
code without manual setup or permission prompts.
What was done?
Claude Code integration
ghcr.io/anthropics/devcontainer-features/claude-code](https://github.com/anthropics/devcontainer-features) as a devcontainer feature
~/.claude/config (credentials, skills, plugins) is staged byinit-host.shand copied into a persistent Docker volume by
post-create.sh, then the staged copy iscleaned up
bypassPermissionsmode is forced in Claude settings so Claude Code runs autonomouslywithout prompts
ANTHROPIC_API_KEYis forwarded from the host environment as a fallback auth method~/.claude/survives container rebuilds (conversationhistory, config)
Optional network firewall (
.devcontainer/init-firewall.sh)whitelisted services only (Anthropic API, npm, crates.io, GitHub, Docker Hub, VS Code
marketplace)
Devcontainer modernization
ghcr.io/dashpay/platform/devcontainer:0.1.0) with alocal Dockerfile build
jq-likes
Docker, LLDB, TOML
wasm-bindgen-clito 0.2.108, addedwasm-packGit worktree support
init-host.shresolves the main.gitdirectory and mounts it into the containerpost-create.shcreates symlinks so git operations work transparently from worktreesDocumentation
.devcontainer/README.mdcovering prerequisites, auth options (OAuth + APIkey), VS Code and CLI usage, firewall setup, persistent data, and troubleshooting
README.mdwith Dev Container as recommended setup methodHow Has This Been Tested?
a git worktree
bypassPermissionsmode and host credentialsworktree
shell history)
Breaking Changes
None. The devcontainer previously pointed to a static image. This replaces it with a
local build — existing users will get the new configuration on next rebuild.
Checklist:
section if my code contains any
For repository code-owners and collaborators only
Summary by CodeRabbit
New Features
Documentation
Chores