From 7e3e842aaae39bb4ced9e4e019be09851ddcda20 Mon Sep 17 00:00:00 2001 From: DevCats Date: Thu, 5 Feb 2026 09:18:27 -0600 Subject: [PATCH] fix: temp-fix for not using coder_env to set path due to limitations (#699) ### Summary Temporary workaround for non-deterministic PATH handling when using `coder_env` across multiple modules ([coder/coder#21885](https://github.com/coder/coder/issues/21885)). ### Problem When multiple modules define `coder_env` with the same `name` (e.g., `PATH`), the final value is non-deterministic due to Go map iteration order. This caused PATH overwrites instead of appending, breaking Claude Code discovery in workspaces using multiple modules. ### Solution Replace `coder_env` PATH manipulation with script-based PATH handling: - **Install script**: Exports PATH and adds claude binary directory to shell profiles (`.profile`, `.bashrc`, `.zshrc`, fish) for interactive shell access - **Start script**: Exports PATH at script execution time - **Symlink**: Creates symlink in `CODER_SCRIPT_BIN_DIR` as additional fallback - **Validation**: Prevents invalid configuration where `claude_binary_path` is customized but `install_claude_code=true` (official installer doesn't support custom paths) ### Changes - Removed `coder_env` resource for PATH - Added PATH export to `install.sh` and `start.sh` - Added shell profile modifications for cross-shell compatibility (bash, zsh, fish) - Added variable validation for `claude_binary_path` ### Note This is a temporary fix until [coder/coder#21885](https://github.com/coder/coder/issues/21885) is resolved with a proper `merge_strategy` attribute for `coder_env`. ## Type of Change - [ ] New module - [ ] New template - [X] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information **Path:** `registry/coder/modules/claude-code` **New version:** `v4.7.5` **Breaking change:** [ ] Yes [X] No ## Testing & Validation - [X] Tests pass (`bun test`) - [X] Code formatted (`bun fmt`) - [X] Changes tested locally ## Related Issues ([coder/coder#21885](https://github.com/coder/coder/issues/21885)) --- registry/coder/modules/claude-code/README.md | 18 ++--- registry/coder/modules/claude-code/main.tf | 56 +++++++--------- .../modules/claude-code/scripts/install.sh | 67 ++++++++++++------- .../modules/claude-code/scripts/start.sh | 5 ++ 4 files changed, 80 insertions(+), 66 deletions(-) diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index a58ed223..340eb175 100644 --- a/registry/coder/modules/claude-code/README.md +++ b/registry/coder/modules/claude-code/README.md @@ -13,7 +13,7 @@ Run the [Claude Code](https://docs.anthropic.com/en/docs/agents-and-tools/claude ```tf module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.7.4" + version = "4.7.5" agent_id = coder_agent.main.id workdir = "/home/coder/project" claude_api_key = "xxxx-xxxxx-xxxx" @@ -47,7 +47,7 @@ By default, when `enable_boundary = true`, the module uses `coder boundary` subc ```tf module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.7.4" + version = "4.7.5" agent_id = coder_agent.main.id workdir = "/home/coder/project" enable_boundary = true @@ -68,7 +68,7 @@ For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage ```tf module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.7.4" + version = "4.7.5" agent_id = coder_agent.main.id workdir = "/home/coder/project" enable_aibridge = true @@ -97,7 +97,7 @@ data "coder_task" "me" {} module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.7.4" + version = "4.7.5" agent_id = coder_agent.main.id workdir = "/home/coder/project" ai_prompt = data.coder_task.me.prompt @@ -120,7 +120,7 @@ This example shows additional configuration options for version pinning, custom ```tf module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.7.4" + version = "4.7.5" agent_id = coder_agent.main.id workdir = "/home/coder/project" @@ -176,7 +176,7 @@ Run and configure Claude Code as a standalone CLI in your workspace. ```tf module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.7.4" + version = "4.7.5" agent_id = coder_agent.main.id workdir = "/home/coder/project" install_claude_code = true @@ -198,7 +198,7 @@ variable "claude_code_oauth_token" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.7.4" + version = "4.7.5" agent_id = coder_agent.main.id workdir = "/home/coder/project" claude_code_oauth_token = var.claude_code_oauth_token @@ -271,7 +271,7 @@ resource "coder_env" "bedrock_api_key" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.7.4" + version = "4.7.5" agent_id = coder_agent.main.id workdir = "/home/coder/project" model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0" @@ -328,7 +328,7 @@ resource "coder_env" "google_application_credentials" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.7.4" + version = "4.7.5" agent_id = coder_agent.main.id workdir = "/home/coder/project" model = "claude-sonnet-4@20250514" diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index 3ed4a021..07e3eb5a 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -208,6 +208,11 @@ variable "claude_binary_path" { type = string description = "Directory where the Claude Code binary is located. Use this if Claude is pre-installed or installed outside the module to a non-default location." default = "$HOME/.local/bin" + + validation { + condition = var.claude_binary_path == "$HOME/.local/bin" || !var.install_claude_code + error_message = "Custom claude_binary_path can only be used when install_claude_code is false. The official installer always installs to $HOME/.local/bin and does not support custom paths." + } } variable "install_via_npm" { @@ -290,18 +295,6 @@ resource "coder_env" "disable_autoupdater" { value = "1" } -resource "coder_env" "claude_binary_path" { - agent_id = var.agent_id - name = "PATH" - value = "${var.claude_binary_path}:$PATH" - - lifecycle { - precondition { - condition = var.claude_binary_path == "$HOME/.local/bin" || !var.install_claude_code - error_message = "Custom claude_binary_path can only be used when install_claude_code is false. The official installer and npm both install to fixed locations." - } - } -} resource "coder_env" "anthropic_model" { count = var.model != "" ? 1 : 0 @@ -382,26 +375,27 @@ module "agentapi" { pre_install_script = var.pre_install_script post_install_script = var.post_install_script start_script = <<-EOT - #!/bin/bash - set -o errexit - set -o pipefail - echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh - chmod +x /tmp/start.sh + #!/bin/bash + set -o errexit + set -o pipefail + echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh + chmod +x /tmp/start.sh - ARG_RESUME_SESSION_ID='${var.resume_session_id}' \ - ARG_CONTINUE='${var.continue}' \ - ARG_DANGEROUSLY_SKIP_PERMISSIONS='${var.dangerously_skip_permissions}' \ - ARG_PERMISSION_MODE='${var.permission_mode}' \ - ARG_WORKDIR='${local.workdir}' \ - ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \ - ARG_REPORT_TASKS='${var.report_tasks}' \ - ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \ - ARG_BOUNDARY_VERSION='${var.boundary_version}' \ - ARG_COMPILE_FROM_SOURCE='${var.compile_boundary_from_source}' \ - ARG_USE_BOUNDARY_DIRECTLY='${var.use_boundary_directly}' \ - ARG_CODER_HOST='${local.coder_host}' \ - /tmp/start.sh - EOT + ARG_RESUME_SESSION_ID='${var.resume_session_id}' \ + ARG_CONTINUE='${var.continue}' \ + ARG_DANGEROUSLY_SKIP_PERMISSIONS='${var.dangerously_skip_permissions}' \ + ARG_PERMISSION_MODE='${var.permission_mode}' \ + ARG_WORKDIR='${local.workdir}' \ + ARG_AI_PROMPT='${base64encode(var.ai_prompt)}' \ + ARG_REPORT_TASKS='${var.report_tasks}' \ + ARG_ENABLE_BOUNDARY='${var.enable_boundary}' \ + ARG_BOUNDARY_VERSION='${var.boundary_version}' \ + ARG_COMPILE_FROM_SOURCE='${var.compile_boundary_from_source}' \ + ARG_USE_BOUNDARY_DIRECTLY='${var.use_boundary_directly}' \ + ARG_CODER_HOST='${local.coder_host}' \ + ARG_CLAUDE_BINARY_PATH='${var.claude_binary_path}' \ + /tmp/start.sh + EOT install_script = <<-EOT #!/bin/bash diff --git a/registry/coder/modules/claude-code/scripts/install.sh b/registry/coder/modules/claude-code/scripts/install.sh index d87a83b7..9a393965 100644 --- a/registry/coder/modules/claude-code/scripts/install.sh +++ b/registry/coder/modules/claude-code/scripts/install.sh @@ -12,6 +12,7 @@ ARG_CLAUDE_CODE_VERSION=${ARG_CLAUDE_CODE_VERSION:-} ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"} ARG_INSTALL_CLAUDE_CODE=${ARG_INSTALL_CLAUDE_CODE:-} ARG_CLAUDE_BINARY_PATH=${ARG_CLAUDE_BINARY_PATH:-"$HOME/.local/bin"} +ARG_CLAUDE_BINARY_PATH=$(eval echo "$ARG_CLAUDE_BINARY_PATH") ARG_INSTALL_VIA_NPM=${ARG_INSTALL_VIA_NPM:-false} ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true} ARG_MCP_APP_STATUS_SLUG=${ARG_MCP_APP_STATUS_SLUG:-} @@ -21,6 +22,8 @@ ARG_ALLOWED_TOOLS=${ARG_ALLOWED_TOOLS:-} ARG_DISALLOWED_TOOLS=${ARG_DISALLOWED_TOOLS:-} ARG_ENABLE_AIBRIDGE=${ARG_ENABLE_AIBRIDGE:-false} +export PATH="$ARG_CLAUDE_BINARY_PATH:$PATH" + echo "--------------------------------" printf "ARG_CLAUDE_CODE_VERSION: %s\n" "$ARG_CLAUDE_CODE_VERSION" @@ -51,39 +54,51 @@ function add_mcp_servers() { done < <(echo "$mcp_json" | jq -r '.mcpServers | to_entries[] | .key, (.value | @json)') } +function add_path_to_shell_profiles() { + local path_dir="$1" + + for profile in "$HOME/.profile" "$HOME/.bash_profile" "$HOME/.bashrc" "$HOME/.zprofile" "$HOME/.zshrc"; do + if [ -f "$profile" ]; then + if ! grep -q "$path_dir" "$profile" 2> /dev/null; then + echo "export PATH=\"\$PATH:$path_dir\"" >> "$profile" + echo "Added $path_dir to $profile" + fi + fi + done + + local fish_config="$HOME/.config/fish/config.fish" + if [ -f "$fish_config" ]; then + if ! grep -q "$path_dir" "$fish_config" 2> /dev/null; then + echo "fish_add_path $path_dir" >> "$fish_config" + echo "Added $path_dir to $fish_config" + fi + fi +} + function ensure_claude_in_path() { - if [ -z "${CODER_SCRIPT_BIN_DIR:-}" ]; then - echo "CODER_SCRIPT_BIN_DIR not set, skipping PATH setup" + local CLAUDE_BIN="" + if command -v claude > /dev/null 2>&1; then + CLAUDE_BIN=$(command -v claude) + elif [ -x "$ARG_CLAUDE_BINARY_PATH/claude" ]; then + CLAUDE_BIN="$ARG_CLAUDE_BINARY_PATH/claude" + elif [ -x "$HOME/.local/bin/claude" ]; then + CLAUDE_BIN="$HOME/.local/bin/claude" + fi + + if [ -z "$CLAUDE_BIN" ] || [ ! -x "$CLAUDE_BIN" ]; then + echo "Warning: Could not find claude binary" return fi - if [ ! -e "$CODER_SCRIPT_BIN_DIR/claude" ]; then - local CLAUDE_BIN="" - if command -v claude > /dev/null 2>&1; then - CLAUDE_BIN=$(command -v claude) - elif [ -x "$ARG_CLAUDE_BINARY_PATH/claude" ]; then - CLAUDE_BIN="$ARG_CLAUDE_BINARY_PATH/claude" - elif [ -x "$HOME/.local/bin/claude" ]; then - CLAUDE_BIN="$HOME/.local/bin/claude" - fi + local CLAUDE_DIR + CLAUDE_DIR=$(dirname "$CLAUDE_BIN") - if [ -n "$CLAUDE_BIN" ] && [ -x "$CLAUDE_BIN" ]; then - ln -s "$CLAUDE_BIN" "$CODER_SCRIPT_BIN_DIR/claude" - echo "Created symlink: $CODER_SCRIPT_BIN_DIR/claude -> $CLAUDE_BIN" - else - echo "Warning: Could not find claude binary to symlink" - fi - else - echo "Claude already available in CODER_SCRIPT_BIN_DIR" + if [ -n "${CODER_SCRIPT_BIN_DIR:-}" ] && [ ! -e "$CODER_SCRIPT_BIN_DIR/claude" ]; then + ln -s "$CLAUDE_BIN" "$CODER_SCRIPT_BIN_DIR/claude" + echo "Created symlink: $CODER_SCRIPT_BIN_DIR/claude -> $CLAUDE_BIN" fi - local marker="# Added by claude-code module" - for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do - if [ -f "$profile" ] && ! grep -q "$marker" "$profile" 2> /dev/null; then - printf "\n%s\nexport PATH=\"%s:\$PATH\"\n" "$marker" "$CODER_SCRIPT_BIN_DIR" >> "$profile" - echo "Added $CODER_SCRIPT_BIN_DIR to PATH in $profile" - fi - done + add_path_to_shell_profiles "$CLAUDE_DIR" } function install_claude_code_cli() { diff --git a/registry/coder/modules/claude-code/scripts/start.sh b/registry/coder/modules/claude-code/scripts/start.sh index b20f3833..a38e7146 100644 --- a/registry/coder/modules/claude-code/scripts/start.sh +++ b/registry/coder/modules/claude-code/scripts/start.sh @@ -2,6 +2,11 @@ set -euo pipefail +ARG_CLAUDE_BINARY_PATH=${ARG_CLAUDE_BINARY_PATH:-"$HOME/.local/bin"} +ARG_CLAUDE_BINARY_PATH=$(eval echo "$ARG_CLAUDE_BINARY_PATH") + +export PATH="$ARG_CLAUDE_BINARY_PATH:$PATH" + command_exists() { command -v "$1" > /dev/null 2>&1 }