From e9fa5bfbcdbc15982fc8a66fa7c50ae53434f08b Mon Sep 17 00:00:00 2001 From: Austin Gregg-Smith Date: Sun, 11 Jan 2026 11:41:42 +0000 Subject: [PATCH 1/3] fix: make devcontainer portable across hosts - 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 --- .devcontainer/Dockerfile | 3 +- .../claude-code/devcontainer-feature.json | 15 +-- .devcontainer/claude-code/init-host.sh | 6 + .devcontainer/claude-code/install.sh | 124 +++++++++--------- .devcontainer/devcontainer.json | 1 + 5 files changed, 76 insertions(+), 73 deletions(-) create mode 100755 .devcontainer/claude-code/init-host.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index aebfdcc..af80ba7 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -10,7 +10,8 @@ RUN curl -L -o /usr/local/bin/pixi -fsSL --compressed "https://github.com/prefix USER vscode WORKDIR /home/vscode -RUN echo 'eval "$(pixi completion -s bash)"' >> /home/vscode/.bashrc +RUN echo 'eval "$(pixi completion -s bash)"' >> /home/vscode/.bashrc \ + && echo 'export PATH="$HOME/.pixi/envs/claude-shim/bin:$HOME/.pixi/bin:$PATH"' >> /home/vscode/.profile # Create .ssh directory with proper permissions for SSH config mounts RUN mkdir -p /home/vscode/.ssh && chmod 700 /home/vscode/.ssh diff --git a/.devcontainer/claude-code/devcontainer-feature.json b/.devcontainer/claude-code/devcontainer-feature.json index a1f5f44..06b9ff5 100644 --- a/.devcontainer/claude-code/devcontainer-feature.json +++ b/.devcontainer/claude-code/devcontainer-feature.json @@ -1,8 +1,8 @@ { "name": "Claude Code CLI", "id": "claude-code", - "version": "0.1.0", - "description": "Installs Claude Code CLI globally and mounts configuration directories", + "version": "0.2.0", + "description": "Installs Claude Code CLI via pixi with persistent configuration", "options": {}, "documentationURL": "https://github.com/anthropics/devcontainer-features", "licenseURL": "https://github.com/anthropics/devcontainer-features/blob/main/LICENSE", @@ -16,16 +16,7 @@ "containerEnv": { "CLAUDE_CONFIG_DIR": "/home/vscode/.claude" }, - "dependsOn": { - "ghcr.io/devcontainers/features/node": {} - }, "mounts": [ - "source=${localEnv:HOME}/.claude/CLAUDE.md,target=/home/vscode/.claude/CLAUDE.md,type=bind,ro", - "source=${localEnv:HOME}/.claude/settings.json,target=/home/vscode/.claude/settings.json,type=bind,ro", - "source=${localEnv:HOME}/.claude/.credentials.json,target=/home/vscode/.claude/.credentials.json,type=bind", - "source=${localEnv:HOME}/.claude/.claude.json,target=/home/vscode/.claude/.claude.json,type=bind", - "source=${localEnv:HOME}/.claude/agents,target=/home/vscode/.claude/agents,type=bind,ro", - "source=${localEnv:HOME}/.claude/commands,target=/home/vscode/.claude/commands,type=bind,ro", - "source=${localEnv:HOME}/.claude/hooks,target=/home/vscode/.claude/hooks,type=bind,ro" + "source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind" ] } diff --git a/.devcontainer/claude-code/init-host.sh b/.devcontainer/claude-code/init-host.sh new file mode 100755 index 0000000..7e123bf --- /dev/null +++ b/.devcontainer/claude-code/init-host.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# Initialize Claude Code host directory for devcontainer bind mount +# This script runs on the HOST before the container is created. +# mkdir -p is idempotent - it only creates if missing, won't clobber existing. + +mkdir -p "$HOME/.claude" diff --git a/.devcontainer/claude-code/install.sh b/.devcontainer/claude-code/install.sh index b85f96a..0150df8 100755 --- a/.devcontainer/claude-code/install.sh +++ b/.devcontainer/claude-code/install.sh @@ -2,57 +2,80 @@ set -eu # Claude Code CLI Local Feature Install Script -# Based on: https://github.com/anthropics/devcontainer-features/pull/25 -# Combines CLI installation with configuration directory setup +# Installs Claude Code via pixi and sets up configuration directories -# Function to install Claude Code CLI -install_claude_code() { - echo "Installing Claude Code CLI globally..." +# Function to install pixi if not found +install_pixi() { + echo "Installing pixi..." - # Verify Node.js and npm are available (should be installed via dependsOn) - if ! command -v node >/dev/null || ! command -v npm >/dev/null; then - cat <&2; exit 1 ;; + esac -ERROR: Node.js and npm are required but not found! + # Download and install pixi + curl -fsSL "https://github.com/prefix-dev/pixi/releases/latest/download/pixi-${ARCH}-unknown-linux-musl" -o /usr/local/bin/pixi + chmod +x /usr/local/bin/pixi -This should not happen as the Node.js feature is declared in 'dependsOn'. + echo "pixi installed successfully" + pixi --version +} -Please check: -1. The devcontainer feature specification is correct -2. The Node.js feature (ghcr.io/devcontainers/features/node) is available -3. Your devcontainer build logs for errors +# Function to install Claude Code CLI via pixi +install_claude_code() { + echo "Installing Claude Code CLI via pixi..." -EOF - exit 1 + # Install pixi if not available + if ! command -v pixi >/dev/null; then + install_pixi fi - # 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 + # Run as target user so it installs to their home directory + if [ "$(id -u)" -eq 0 ] && [ "$target_user" != "root" ]; then + su - "$target_user" -c "pixi global install --channel https://prefix.dev/blooop claude-shim" + else + pixi global install --channel https://prefix.dev/blooop claude-shim + fi - # Verify installation - if command -v claude >/dev/null; then + # Add pixi paths to user's profile if not already there + local profile="$target_home/.profile" + local pixi_path_line='export PATH="$HOME/.pixi/envs/claude-shim/bin:$HOME/.pixi/bin:$PATH"' + if [ -f "$profile" ] && ! grep -q "\.pixi/envs/claude-shim/bin" "$profile"; then + echo "$pixi_path_line" >> "$profile" + elif [ ! -f "$profile" ]; then + echo "$pixi_path_line" > "$profile" + chown "$target_user:$target_user" "$profile" 2>/dev/null || true + fi + + # Verify installation by checking the binary exists + local pixi_bin_path="$target_home/.pixi/bin" + local claude_bin="$pixi_bin_path/claude" + if [ -x "$claude_bin" ]; then echo "Claude Code CLI installed successfully!" - claude --version + "$claude_bin" --version return 0 else - echo "ERROR: Claude Code CLI installation failed!" + echo "ERROR: Claude Code CLI installation failed! Binary not found at $claude_bin" return 1 fi } # Function to create Claude configuration directories -# These directories will be mounted from the host, but we create them -# in the container to ensure they exist and have proper permissions create_claude_directories() { echo "Creating Claude configuration directories..." # Determine the target user's home directory - # $_REMOTE_USER is set by devcontainer, fallback to 'vscode' local target_user="${_REMOTE_USER:-vscode}" local target_home="${_REMOTE_USER_HOME:-/home/${target_user}}" - # Be defensive: if the resolved home does not exist, fall back to $HOME, - # then to /home/${target_user}. If neither is available, fail clearly. + # Be defensive: if the resolved home does not exist, fall back if [ ! -d "$target_home" ]; then if [ -n "${HOME:-}" ] && [ -d "$HOME" ]; then echo "Warning: target_home '$target_home' does not exist, falling back to \$HOME: $HOME" >&2 @@ -61,11 +84,7 @@ create_claude_directories() { echo "Warning: target_home '$target_home' does not exist, falling back to /home/${target_user}" >&2 target_home="/home/${target_user}" else - echo "Error: No suitable home directory found for '${target_user}'. Tried:" >&2 - echo " - _REMOTE_USER_HOME='${_REMOTE_USER_HOME:-}'" >&2 - echo " - \$HOME='${HOME:-}'" >&2 - echo " - /home/${target_user}" >&2 - echo "Please set _REMOTE_USER_HOME to a valid, writable directory." >&2 + echo "Error: No suitable home directory found for '${target_user}'." >&2 exit 1 fi fi @@ -73,14 +92,13 @@ create_claude_directories() { echo "Target home directory: $target_home" echo "Target user: $target_user" - # Create the main .claude directory + # Create the main .claude directory and subdirectories mkdir -p "$target_home/.claude" mkdir -p "$target_home/.claude/agents" mkdir -p "$target_home/.claude/commands" mkdir -p "$target_home/.claude/hooks" # Create empty config files if they don't exist - # This ensures the bind mounts won't fail if files are missing on host if [ ! -f "$target_home/.claude/.credentials.json" ]; then echo "{}" > "$target_home/.claude/.credentials.json" chmod 600 "$target_home/.claude/.credentials.json" @@ -92,9 +110,6 @@ create_claude_directories() { fi # Set proper ownership - # Note: These will be overridden by bind mounts from the host, - # but this ensures the directories exist with correct permissions - # if the mounts fail or for non-mounted directories if [ "$(id -u)" -eq 0 ]; then chown -R "$target_user:$target_user" "$target_home/.claude" || true fi @@ -102,16 +117,21 @@ create_claude_directories() { echo "Claude directories created successfully" } -# Main script starts here +# Main script main() { echo "=========================================" echo "Activating feature 'claude-code' (local)" echo "=========================================" - # Install Claude Code CLI (or verify it's already installed) - if command -v claude >/dev/null; then + # Determine target paths + local target_user="${_REMOTE_USER:-vscode}" + local target_home="${_REMOTE_USER_HOME:-/home/${target_user}}" + local claude_bin="$target_home/.pixi/bin/claude" + + # Install Claude Code CLI + if [ -x "$claude_bin" ]; then echo "Claude Code CLI is already installed" - claude --version + "$claude_bin" --version else install_claude_code || exit 1 fi @@ -123,27 +143,11 @@ main() { echo "Claude Code feature activated successfully!" echo "=========================================" echo "" - echo "Configuration files mounted from host:" - echo " Read-Write (auth & state):" - echo " - ~/.claude/.credentials.json (OAuth tokens)" - echo " - ~/.claude/.claude.json (account, setup tracking)" - echo "" - echo " Read-Only (security-protected):" - echo " - ~/.claude/CLAUDE.md" - echo " - ~/.claude/settings.json" - echo " - ~/.claude/agents/" - echo " - ~/.claude/commands/" - echo " - ~/.claude/hooks/" - echo "" - echo "Authentication:" - echo " - If you're already authenticated on your host, credentials are shared" - echo " - Otherwise, run 'claude' and follow the OAuth flow" - echo " - The OAuth callback may open in your host browser" - echo " - Credentials are stored on your host at ~/.claude/.credentials.json" + echo "Configuration is stored in a Docker volume (claude-config)" + echo "and persists between container rebuilds." echo "" - echo "To modify config files, edit on your host machine and rebuild the container." + echo "To authenticate, run 'claude' and follow the OAuth flow." echo "" } -# Execute main function main diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6906159..e6492f7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,6 +4,7 @@ "dockerfile": "Dockerfile", "context": ".." }, + "initializeCommand": ".devcontainer/claude-code/init-host.sh", "customizations": { "vscode": { "settings": {}, From 6330d335a31707568c6946d42c12eb36a0decf74 Mon Sep 17 00:00:00 2001 From: Austin Gregg-Smith Date: Sun, 11 Jan 2026 11:51:46 +0000 Subject: [PATCH 2/3] Address code review: remove hardcoded paths, improve home resolution - 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 --- .devcontainer/Dockerfile | 2 +- .devcontainer/claude-code/install.sh | 117 ++++++++++++++++----------- 2 files changed, 71 insertions(+), 48 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index af80ba7..e7905f8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -11,7 +11,7 @@ USER vscode WORKDIR /home/vscode RUN echo 'eval "$(pixi completion -s bash)"' >> /home/vscode/.bashrc \ - && echo 'export PATH="$HOME/.pixi/envs/claude-shim/bin:$HOME/.pixi/bin:$PATH"' >> /home/vscode/.profile + && echo 'export PATH="$HOME/.pixi/bin:$PATH"' >> /home/vscode/.profile # Create .ssh directory with proper permissions for SSH config mounts RUN mkdir -p /home/vscode/.ssh && chmod 700 /home/vscode/.ssh diff --git a/.devcontainer/claude-code/install.sh b/.devcontainer/claude-code/install.sh index 0150df8..62ef15c 100755 --- a/.devcontainer/claude-code/install.sh +++ b/.devcontainer/claude-code/install.sh @@ -4,6 +4,44 @@ set -eu # Claude Code CLI Local Feature Install Script # Installs Claude Code via pixi and sets up configuration directories +# Global variables set by resolve_target_home +TARGET_USER="" +TARGET_HOME="" + +# Function to resolve target user and home directory with validation +# Sets TARGET_USER and TARGET_HOME global variables +resolve_target_home() { + TARGET_USER="${_REMOTE_USER:-vscode}" + TARGET_HOME="${_REMOTE_USER_HOME:-}" + + # If _REMOTE_USER_HOME is not set, try to infer from current user or /home/ + if [ -z "${TARGET_HOME}" ]; then + if [ "$(id -un 2>/dev/null)" = "${TARGET_USER}" ] && [ -n "${HOME:-}" ]; then + TARGET_HOME="${HOME}" + elif [ -d "/home/${TARGET_USER}" ]; then + TARGET_HOME="/home/${TARGET_USER}" + fi + fi + + # If TARGET_HOME is set but doesn't exist, try fallbacks + if [ -n "${TARGET_HOME}" ] && [ ! -d "${TARGET_HOME}" ]; then + if [ -n "${HOME:-}" ] && [ -d "$HOME" ]; then + echo "Warning: TARGET_HOME '${TARGET_HOME}' does not exist, falling back to \$HOME: $HOME" >&2 + TARGET_HOME="$HOME" + elif [ -d "/home/${TARGET_USER}" ]; then + echo "Warning: TARGET_HOME '${TARGET_HOME}' does not exist, falling back to /home/${TARGET_USER}" >&2 + 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 +} + # Function to install pixi if not found install_pixi() { echo "Installing pixi..." @@ -32,30 +70,30 @@ install_claude_code() { install_pixi fi - # Determine target user for pixi global install - local target_user="${_REMOTE_USER:-vscode}" - local target_home="${_REMOTE_USER_HOME:-/home/${target_user}}" + # Resolve target user and home (sets TARGET_USER and TARGET_HOME) + resolve_target_home # Install with pixi global from blooop channel # Run as target user so it installs to their home directory - if [ "$(id -u)" -eq 0 ] && [ "$target_user" != "root" ]; then - su - "$target_user" -c "pixi global install --channel https://prefix.dev/blooop claude-shim" + if [ "$(id -u)" -eq 0 ] && [ "$TARGET_USER" != "root" ]; then + su - "$TARGET_USER" -c "pixi global install --channel https://prefix.dev/blooop claude-shim" else pixi global install --channel https://prefix.dev/blooop claude-shim fi - # Add pixi paths to user's profile if not already there - local profile="$target_home/.profile" - local pixi_path_line='export PATH="$HOME/.pixi/envs/claude-shim/bin:$HOME/.pixi/bin:$PATH"' - if [ -f "$profile" ] && ! grep -q "\.pixi/envs/claude-shim/bin" "$profile"; then + # Add pixi bin path to user's profile if not already there + # Only add ~/.pixi/bin - avoid hardcoding env-specific paths that may change + local profile="$TARGET_HOME/.profile" + local pixi_path_line='export PATH="$HOME/.pixi/bin:$PATH"' + if [ -f "$profile" ] && ! grep -q '\.pixi/bin' "$profile"; then echo "$pixi_path_line" >> "$profile" elif [ ! -f "$profile" ]; then echo "$pixi_path_line" > "$profile" - chown "$target_user:$target_user" "$profile" 2>/dev/null || true + chown "$TARGET_USER:$TARGET_USER" "$profile" 2>/dev/null || true fi # Verify installation by checking the binary exists - local pixi_bin_path="$target_home/.pixi/bin" + local pixi_bin_path="$TARGET_HOME/.pixi/bin" local claude_bin="$pixi_bin_path/claude" if [ -x "$claude_bin" ]; then echo "Claude Code CLI installed successfully!" @@ -71,47 +109,32 @@ install_claude_code() { create_claude_directories() { echo "Creating Claude configuration directories..." - # Determine the target user's home directory - local target_user="${_REMOTE_USER:-vscode}" - local target_home="${_REMOTE_USER_HOME:-/home/${target_user}}" + # Resolve target user and home (sets TARGET_USER and TARGET_HOME) + resolve_target_home - # Be defensive: if the resolved home does not exist, fall back - if [ ! -d "$target_home" ]; then - if [ -n "${HOME:-}" ] && [ -d "$HOME" ]; then - echo "Warning: target_home '$target_home' does not exist, falling back to \$HOME: $HOME" >&2 - target_home="$HOME" - elif [ -d "/home/${target_user}" ]; then - echo "Warning: target_home '$target_home' does not exist, falling back to /home/${target_user}" >&2 - target_home="/home/${target_user}" - else - echo "Error: No suitable home directory found for '${target_user}'." >&2 - exit 1 - fi - fi - - echo "Target home directory: $target_home" - echo "Target user: $target_user" + echo "Target home directory: $TARGET_HOME" + echo "Target user: $TARGET_USER" # Create the main .claude directory and subdirectories - mkdir -p "$target_home/.claude" - mkdir -p "$target_home/.claude/agents" - mkdir -p "$target_home/.claude/commands" - mkdir -p "$target_home/.claude/hooks" + mkdir -p "$TARGET_HOME/.claude" + mkdir -p "$TARGET_HOME/.claude/agents" + mkdir -p "$TARGET_HOME/.claude/commands" + mkdir -p "$TARGET_HOME/.claude/hooks" # Create empty config files if they don't exist - if [ ! -f "$target_home/.claude/.credentials.json" ]; then - echo "{}" > "$target_home/.claude/.credentials.json" - chmod 600 "$target_home/.claude/.credentials.json" + if [ ! -f "$TARGET_HOME/.claude/.credentials.json" ]; then + echo "{}" > "$TARGET_HOME/.claude/.credentials.json" + chmod 600 "$TARGET_HOME/.claude/.credentials.json" fi - if [ ! -f "$target_home/.claude/.claude.json" ]; then - echo "{}" > "$target_home/.claude/.claude.json" - chmod 600 "$target_home/.claude/.claude.json" + if [ ! -f "$TARGET_HOME/.claude/.claude.json" ]; then + echo "{}" > "$TARGET_HOME/.claude/.claude.json" + chmod 600 "$TARGET_HOME/.claude/.claude.json" fi # Set proper ownership if [ "$(id -u)" -eq 0 ]; then - chown -R "$target_user:$target_user" "$target_home/.claude" || true + chown -R "$TARGET_USER:$TARGET_USER" "$TARGET_HOME/.claude" || true fi echo "Claude directories created successfully" @@ -123,10 +146,10 @@ main() { echo "Activating feature 'claude-code' (local)" echo "=========================================" - # Determine target paths - local target_user="${_REMOTE_USER:-vscode}" - local target_home="${_REMOTE_USER_HOME:-/home/${target_user}}" - local claude_bin="$target_home/.pixi/bin/claude" + # Resolve target user and home (sets TARGET_USER and TARGET_HOME) + resolve_target_home + + local claude_bin="$TARGET_HOME/.pixi/bin/claude" # Install Claude Code CLI if [ -x "$claude_bin" ]; then @@ -143,8 +166,8 @@ main() { echo "Claude Code feature activated successfully!" echo "=========================================" echo "" - echo "Configuration is stored in a Docker volume (claude-config)" - echo "and persists between container rebuilds." + echo "Configuration is bind-mounted from the host (~/.claude)" + echo "and persists across container rebuilds." echo "" echo "To authenticate, run 'claude' and follow the OAuth flow." echo "" From 2cd30e76db0a5d9fd04c13f998381443a2651e61 Mon Sep 17 00:00:00 2001 From: Austin Gregg-Smith Date: Sun, 11 Jan 2026 11:57:20 +0000 Subject: [PATCH 3/3] Fix pixi trampoline bug and defer claude download to first run - 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 --- .devcontainer/Dockerfile | 4 +++- .devcontainer/claude-code/install.sh | 14 ++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index e7905f8..8b783b9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -11,7 +11,9 @@ USER vscode WORKDIR /home/vscode RUN echo 'eval "$(pixi completion -s bash)"' >> /home/vscode/.bashrc \ - && echo 'export PATH="$HOME/.pixi/bin:$PATH"' >> /home/vscode/.profile + && echo 'export PATH="$HOME/.pixi/bin:$PATH"' >> /home/vscode/.profile \ + && echo '# Workaround: pixi trampoline fails for bash scripts, so add env bin directly' >> /home/vscode/.profile \ + && echo '[ -d "$HOME/.pixi/envs/claude-shim/bin" ] && export PATH="$HOME/.pixi/envs/claude-shim/bin:$PATH"' >> /home/vscode/.profile # Create .ssh directory with proper permissions for SSH config mounts RUN mkdir -p /home/vscode/.ssh && chmod 700 /home/vscode/.ssh diff --git a/.devcontainer/claude-code/install.sh b/.devcontainer/claude-code/install.sh index 62ef15c..020018f 100755 --- a/.devcontainer/claude-code/install.sh +++ b/.devcontainer/claude-code/install.sh @@ -82,7 +82,6 @@ install_claude_code() { fi # Add pixi bin path to user's profile if not already there - # Only add ~/.pixi/bin - avoid hardcoding env-specific paths that may change local profile="$TARGET_HOME/.profile" local pixi_path_line='export PATH="$HOME/.pixi/bin:$PATH"' if [ -f "$profile" ] && ! grep -q '\.pixi/bin' "$profile"; then @@ -92,12 +91,20 @@ install_claude_code() { chown "$TARGET_USER:$TARGET_USER" "$profile" 2>/dev/null || true fi - # Verify installation by checking the binary exists + # Workaround: pixi trampoline fails for bash scripts, so add env bin directly + # This conditionally adds the path only if the env exists + local env_path_line='[ -d "$HOME/.pixi/envs/claude-shim/bin" ] && export PATH="$HOME/.pixi/envs/claude-shim/bin:$PATH"' + if [ -f "$profile" ] && ! grep -q 'pixi/envs/claude-shim' "$profile"; then + echo "# Workaround: pixi trampoline fails for bash scripts" >> "$profile" + echo "$env_path_line" >> "$profile" + fi + + # Verify installation by checking the trampoline exists (don't run it - that triggers download) local pixi_bin_path="$TARGET_HOME/.pixi/bin" local claude_bin="$pixi_bin_path/claude" if [ -x "$claude_bin" ]; then echo "Claude Code CLI installed successfully!" - "$claude_bin" --version + echo "(Claude binary will be downloaded on first run)" return 0 else echo "ERROR: Claude Code CLI installation failed! Binary not found at $claude_bin" @@ -154,7 +161,6 @@ main() { # Install Claude Code CLI if [ -x "$claude_bin" ]; then echo "Claude Code CLI is already installed" - "$claude_bin" --version else install_claude_code || exit 1 fi