Skip to content

Conversation

@blooop
Copy link
Owner

@blooop blooop commented Jan 11, 2026

Summary

  • Add init-host.sh script to create ~/.claude directory on host before container starts
  • Switch from npm/Node.js to pixi for Claude Code installation using the claude-shim package
  • Use single bind mount for ~/.claude folder instead of individual file mounts that fail on fresh hosts
  • Add pixi bin paths to .profile for proper PATH setup (bypasses pixi trampoline issue with bash scripts)
  • Auto-install pixi if not found in base image - feature is now fully self-contained

Changes

  • .devcontainer/claude-code/init-host.sh (new): Creates ~/.claude on host if missing
  • .devcontainer/claude-code/devcontainer-feature.json: Simplified mounts, removed Node.js dependency
  • .devcontainer/claude-code/install.sh:
    • Uses pixi global install claude-shim instead of npm
    • Installs pixi automatically if not found
    • Adds PATH setup to user's .profile
  • .devcontainer/Dockerfile: Adds pixi bin paths to PATH
  • .devcontainer/devcontainer.json: Adds initializeCommand to run host init script

Test plan

  • Tested devpod up . on fresh host with no ~/.claude directory
  • Verified claude works: claude --version returns 2.1.2 (Claude Code)
  • Credentials persist on host and are shared across devcontainers
  • Test on base image without pixi pre-installed

🤖 Generated with Claude Code

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 11, 2026

Reviewer's Guide

Switches Claude Code devcontainer feature from a Node/npm-based installation to a pixi-based claude-shim installation, simplifies host mounts by using a single ~/.claude bind plus a host init script, and ensures the pixi bin paths are on PATH so the claude CLI works across fresh hosts and different environments.

Sequence diagram for devcontainer startup with pixi-based Claude Code installation

sequenceDiagram
    actor DevUser as Dev_user
    participant Host as Host_machine
    participant InitHost as init_host_sh
    participant DevPod as Devpod
    participant Docker as Docker_engine
    participant Container as Devcontainer
    participant Feature as claude_code_feature_install_sh
    participant Pixi as pixi

    DevUser->>DevPod: devpod up .
    DevPod->>Host: Read devcontainer_json
    DevPod->>InitHost: initializeCommand init_host_sh
    InitHost->>Host: mkdir -p $HOME/.claude

    DevPod->>Docker: Build devcontainer image
    Docker->>Container: Create container with bind mount ~/.claude

    DevPod->>Container: Start container
    Container->>Feature: Run feature install script

    Feature->>Container: Resolve target_user and target_home
    Feature->>Container: Check claude at target_home/.pixi/bin/claude
    alt claude_binary_missing
        Feature->>Container: Check command -v pixi
        alt pixi_missing
            Feature-->>DevUser: Print error and exit
        else pixi_available
            alt running_as_root_and_target_user_not_root
                Feature->>Pixi: su - target_user -c "pixi global install claude-shim"
            else non_root_or_target_user_root
                Feature->>Pixi: pixi global install claude-shim
            end
            Feature->>Container: Verify target_home/.pixi/bin/claude exists
        end
    else claude_binary_present
        Feature->>Container: Print existing claude version
    end

    Feature->>Container: create_claude_directories
    Feature->>Container: mkdir -p ~/.claude and subdirs
    Feature->>Container: Ensure config files exist
    Feature->>Container: chown -R target_user:target_user ~/.claude

    Feature-->>DevUser: Print success and usage message
    DevUser->>Container: claude --version
    Container->>Pixi: Execute claude from pixi bin
    Pixi-->>DevUser: Display Claude Code version
Loading

Flow diagram for claude-code install.sh with pixi-based installation

flowchart TD
    A[Start main] --> B[Resolve target_user and target_home]
    B --> C{claude at target_home/.pixi/bin/claude exists?}
    C -- Yes --> D[Print existing Claude Code version]
    D --> H[create_claude_directories]
    C -- No --> E{command -v pixi succeeds?}
    E -- No --> F[Print pixi required error and exit 1]
    E -- Yes --> G{running as root and target_user not root?}
    G -- Yes --> G1[Run su - target_user -c 'pixi global install --channel blooop claude-shim']
    G -- No --> G2[Run pixi global install --channel blooop claude-shim]
    G1 --> I[Verify target_home/.pixi/bin/claude exists]
    G2 --> I[Verify target_home/.pixi/bin/claude exists]
    I -->|Exists| H[create_claude_directories]
    I -->|Missing| J[Print installation failed, exit 1]

    H --> H1[mkdir -p ~/.claude and subdirectories]
    H1 --> H2[Create .credentials.json and .claude.json if missing]
    H2 --> H3[Set chmod 600 on credentials, 644 on config]
    H3 --> H4{running as root?}
    H4 -- Yes --> H5[chown -R target_user:target_user ~/.claude]
    H4 -- No --> H6[Skip chown]
    H5 --> K[Print feature activated and usage]
    H6 --> K[Print feature activated and usage]
    K --> L[End main]
Loading

File-Level Changes

Change Details Files
Replace npm/Node.js-based Claude Code installation with pixi-based claude-shim global install, resolving the correct target user and binary location.
  • Require pixi instead of node/npm and update error messaging to reference pixi installation
  • Install claude-shim via pixi global install --channel https://prefix.dev/blooop using the devcontainer remote user or vscode as default
  • Run pixi as the non-root target user when possible so binaries land in their home directory
  • Verify installation by checking for $target_home/.pixi/bin/claude and running it to show the version
  • Change main flow to treat Claude as installed when the claude binary exists at the computed pixi bin path instead of relying on command -v
.devcontainer/claude-code/install.sh
Tighten and simplify Claude config directory setup to match the new single-mount strategy and volume-based persistence.
  • Keep defensive resolution of the target home directory but simplify the failure path and logging when no suitable home is found
  • Create the ~/.claude directory tree and basic config files (credentials and .claude.json) with appropriate permissions
  • Ensure ownership is corrected for the target user when running as root
  • Update user-facing messaging to refer to Docker volume-based persistence instead of individual host-mounted files and simplify auth instructions
.devcontainer/claude-code/install.sh
Ensure pixi-installed Claude binaries are on PATH for interactive shells in the devcontainer.
  • Append pixi bash completion setup to the vscode user .bashrc
  • Add PATH exports for $HOME/.pixi/envs/claude-shim/bin and $HOME/.pixi/bin to the vscode user .profile so login shells see the claude binary
.devcontainer/Dockerfile
Add a host-side initialization script and devcontainer wiring so ~/.claude exists on the host before binding, and simplify feature/devcontainer configuration to use a single bind mount.
  • Introduce init-host.sh that creates $HOME/.claude on the host in an idempotent way
  • Wire the new init-host script into devcontainer.json via initializeCommand so it runs before the container starts
  • Update the claude-code devcontainer feature definition to remove Node.js dependencies and align mounts with the new single ~/.claude bind or volume-based approach
.devcontainer/claude-code/init-host.sh
.devcontainer/devcontainer.json
.devcontainer/claude-code/devcontainer-feature.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The hardcoded PATH entries for $HOME/.pixi/envs/claude-shim/bin assume a specific pixi global env name; consider deriving the actual bin paths from pixi info or relying on ~/.pixi/bin only to avoid breaking if the env layout or name changes.
  • The install.sh messaging about configuration being stored in a claude-config Docker volume appears inconsistent with the new host ~/.claude initialization and bind-mount approach; update the user-facing output so it accurately reflects the current storage and persistence behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The hardcoded PATH entries for `$HOME/.pixi/envs/claude-shim/bin` assume a specific pixi global env name; consider deriving the actual bin paths from `pixi info` or relying on `~/.pixi/bin` only to avoid breaking if the env layout or name changes.
- The `install.sh` messaging about configuration being stored in a `claude-config` Docker volume appears inconsistent with the new host `~/.claude` initialization and bind-mount approach; update the user-facing output so it accurately reflects the current storage and persistence behavior.

## Individual Comments

### Comment 1
<location> `.devcontainer/claude-code/install.sh:25-26` </location>
<code_context>
-    # Install with npm
-    npm install -g @anthropic-ai/claude-code
+    # Determine target user for pixi global install
+    local target_user="${_REMOTE_USER:-vscode}"
+    local target_home="${_REMOTE_USER_HOME:-/home/${target_user}}"
+
+    # Install with pixi global from blooop channel
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Home directory resolution in `install_claude_code` is less defensive than in `create_claude_directories` and can point to a non-existent path.

`create_claude_directories` does a full fallback chain for the home directory (`_REMOTE_USER_HOME``$HOME``/home/${target_user}`) and errors if none exist, but `install_claude_code` just uses `_REMOTE_USER_HOME` or `/home/${target_user}` without checking the path exists. If `_REMOTE_USER_HOME` is wrong or missing, install will succeed for the user but the verification step and `claude_bin` resolution in `main()` can point at a non-existent directory and falsely report the CLI as missing.

Consider extracting the home-resolution logic into a shared helper (or at least applying the same fallback + existence checks here) so installation and verification both rely on a validated `target_home`.

Suggested implementation:

```
    # Determine target user for pixi global install
    local target_user="${_REMOTE_USER:-vscode}"

    # Resolve target home directory with fallbacks and validation
    local target_home="${_REMOTE_USER_HOME:-}"

    # If _REMOTE_USER_HOME is not set, try to infer from current user or /home/<user>
    if [ -z "${target_home}" ]; then
        # If we're already running as the target user and HOME is set, prefer that
        if [ "$(id -un 2>/dev/null)" = "${target_user}" ] && [ -n "${HOME}" ]; then
            target_home="${HOME}"
        # Otherwise fall back to the conventional /home/<user> path if it exists
        elif [ -d "/home/${target_user}" ]; then
            target_home="/home/${target_user}"
        fi
    fi

    # Ensure we ended up with a valid, existing home directory
    if [ -z "${target_home}" ] || [ ! -d "${target_home}" ]; then
        echo "Error: could not determine a valid home directory for user '${target_user}'." >&2
        echo "Checked _REMOTE_USER_HOME ('${_REMOTE_USER_HOME:-}'), \$HOME ('${HOME:-}'), and /home/${target_user}." >&2
        exit 1
    fi

    # Install with pixi global from blooop channel

```

To fully align installation and verification:

1. Extract this home-resolution logic into a shared helper function (e.g. `resolve_target_home()`) near the top of the script.
2. Update `create_claude_directories` to call that helper instead of duplicating the logic, so both functions rely on the same validated `target_home`.
3. Ensure `main()` and any `claude_bin` / verification logic also call the same helper (or use the same `target_home` variable) rather than recomputing the home path differently.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- Add init-host.sh to create ~/.claude directory on host before container starts
- Use bind mount for ~/.claude folder instead of individual file mounts
- Switch from npm to pixi for claude installation (claude-shim package)
- Remove Node.js dependency from claude-code feature
- Add pixi bin paths to .profile for proper PATH setup
- Add initializeCommand to devcontainer.json to run host init script
- Install pixi automatically if not found in base image
- Feature now adds PATH setup to user profile (self-contained)

This allows the devcontainer to work on fresh hosts without requiring
pre-existing ~/.claude files, while still sharing credentials across
all devcontainers.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@blooop blooop force-pushed the fix/portable-devcontainer-claude branch from cb8ede9 to e9fa5bf Compare January 11, 2026 11:44
blooop and others added 2 commits January 11, 2026 11:51
- Remove hardcoded $HOME/.pixi/envs/claude-shim/bin from PATH in both
  Dockerfile and install.sh - rely on ~/.pixi/bin only
- Add resolve_target_home() helper function for consistent, defensive
  home directory resolution with proper fallbacks
- Update messaging to say "bind-mounted from host" instead of
  "Docker volume"

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add conditional PATH entry for ~/.pixi/envs/claude-shim/bin to work
  around pixi trampoline bug that fails for bash script executables
- Remove claude --version calls during feature install to avoid
  downloading the Claude binary during container build
- Claude binary will now be downloaded on first user invocation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@blooop blooop merged commit 7188153 into main Jan 11, 2026
7 checks passed
@blooop blooop deleted the fix/portable-devcontainer-claude branch January 11, 2026 12:21
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.

2 participants