diff --git a/src/claude-code/README.md b/src/claude-code/README.md index c383602..f472851 100644 --- a/src/claude-code/README.md +++ b/src/claude-code/README.md @@ -11,10 +11,23 @@ Installs the Claude Code CLI globally } ``` +### Installing as a user (recommended for non-root containers) + +If you run your devcontainer as a non-root user (e.g., `vscode` user), use the `installAsUser` option to prevent permission issues with auto-updates: + +```json +"features": { + "ghcr.io/anthropics/devcontainer-features/claude-code:1": { + "installAsUser": true + } +} +``` + ## Options | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| +| installAsUser | Install Claude Code locally for the current user instead of globally. This prevents permission issues with auto-updates when running as a non-root user. | boolean | false | ## Customizations @@ -60,6 +73,19 @@ If your container already has Node.js installed (for example, a container based When using with containers that have nvm pre-installed, you can use the Claude Code feature directly, and it will use the existing Node.js installation. +## User Installation vs Global Installation + +### Global Installation (default) +- Installs Claude Code system-wide +- Requires root permissions for updates +- May cause permission issues when running as non-root user + +### User Installation (`installAsUser: true`) +- Installs Claude Code to `~/.local/bin` +- Allows updates without root permissions +- Recommended when running devcontainers as non-root users +- Automatically configures PATH in shell profiles + --- diff --git a/src/claude-code/devcontainer-feature.json b/src/claude-code/devcontainer-feature.json index c9ba494..1bff615 100644 --- a/src/claude-code/devcontainer-feature.json +++ b/src/claude-code/devcontainer-feature.json @@ -1,9 +1,15 @@ { "name": "Claude Code CLI", "id": "claude-code", - "version": "1.0.5", - "description": "Installs the Claude Code CLI globally", - "options": {}, + "version": "1.1.0", + "description": "Installs the Claude Code CLI globally or locally for the current user", + "options": { + "installAsUser": { + "type": "boolean", + "default": false, + "description": "Install Claude Code locally for the current user instead of globally. This prevents permission issues with auto-updates when running as a non-root user." + } + }, "documentationURL": "https://github.com/anthropics/devcontainer-features/tree/main/src/claude-code", "licenseURL": "https://github.com/anthropics/devcontainer-features/blob/main/LICENSE", "customizations": { diff --git a/src/claude-code/install.sh b/src/claude-code/install.sh index b115c9e..e75caa8 100755 --- a/src/claude-code/install.sh +++ b/src/claude-code/install.sh @@ -89,9 +89,46 @@ install_nodejs() { # Function to install Claude Code CLI install_claude_code() { - echo "Installing Claude Code CLI..." - npm install -g @anthropic-ai/claude-code + local install_as_user="$1" + + if [ "$install_as_user" = "true" ]; then + echo "Installing Claude Code CLI for current user..." + + # Set npm to install to user directory + export NPM_CONFIG_PREFIX="$HOME/.local" + mkdir -p "$HOME/.local/bin" + + # Install globally but to user directory + npm install -g @anthropic-ai/claude-code + + # Add user bin directory to PATH + if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then + export PATH="$HOME/.local/bin:$PATH" + echo "Added $HOME/.local/bin to PATH" + fi + + # Create a script to ensure PATH is set for the user + cat > "$HOME/.bashrc_claude" << 'EOF' +# Add user-local npm packages to PATH +if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then + export PATH="$HOME/.local/bin:$PATH" +fi +EOF + + # Source the script in common shell configs + for config_file in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do + if [ -f "$config_file" ] && ! grep -q "source.*\.bashrc_claude" "$config_file"; then + echo "source ~/.bashrc_claude" >> "$config_file" + echo "Added Claude Code PATH setup to $config_file" + fi + done + + else + echo "Installing Claude Code CLI globally..." + npm install -g @anthropic-ai/claude-code + fi + # Check if claude command is available if command -v claude >/dev/null; then echo "Claude Code CLI installed successfully!" claude --version @@ -121,6 +158,10 @@ EOF # Main script starts here main() { echo "Activating feature 'claude-code'" + + # Read the installAsUser option (default: false) + local install_as_user="${INSTALLASUSER:-false}" + echo "Install as user: $install_as_user" # Detect package manager PKG_MANAGER=$(detect_package_manager) @@ -133,7 +174,7 @@ main() { fi # Install Claude Code CLI - install_claude_code || exit 1 + install_claude_code "$install_as_user" || exit 1 } # Execute main function diff --git a/test/claude-code/scenarios.json b/test/claude-code/scenarios.json index 0c894f0..c8a9f7c 100644 --- a/test/claude-code/scenarios.json +++ b/test/claude-code/scenarios.json @@ -5,5 +5,14 @@ "ghcr.io/devcontainers/features/node:1": {}, "claude-code": {} } + }, + "user_install": { + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/devcontainers/features/node:1": {}, + "claude-code": { + "installAsUser": true + } + } } } \ No newline at end of file