chore: add input for path, and use coder_env for model in claude-code module (#617)

## Description

Adds a `claude_path` variable to override the path that the `coder_env`
sets for claude-code.
Adds a `install_via_npm` variable to override the official installation
and use npm to install claude-code ( this doesn't change any behaviour,
simply just the options available to the user )

Model now uses `coder_env` to set the `ANTHROPIC_MODEL` env instead of
using the `--model` cli flag which did not set the anthropic model
globally in the workspace.

Normal Usage -> Official Installer
Specific Version Set -> NPM Install
Install Via NPM -> NPM Install

The idea with `claude_path` is that someone who wants to source claude
another way can install it and utilize that install in the module. If
install is true and a custom path is provided there is a tf precondition
that will disallow this.

<!-- Briefly describe what this PR does and why -->

## Type of Change

- [ ] New module
- [ ] New template
- [ ] Bug fix
- [X] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/coder/modules/claude-code`  
**New version:** `v4.4.0`  
**Breaking change:** [ ] Yes [X] No

## Testing & Validation

- [X] Tests pass (`bun test`)
- [X] Code formatted (`bun fmt`)
- [X] Changes tested locally

## Related Issues

<!-- Link related issues or write "None" if not applicable -->
This commit is contained in:
DevCats
2026-01-13 14:12:10 -06:00
committed by GitHub
parent 2a40c07c3a
commit 44a8c66d94
5 changed files with 103 additions and 36 deletions
+13 -9
View File
@@ -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.3.0"
version = "4.4.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -44,8 +44,8 @@ This example shows how to configure the Claude Code module to run the agent behi
```tf
module "claude-code" {
source = "dev.registry.coder.com/coder/claude-code/coder"
version = "4.3.0"
source = "registry.coder.com/coder/claude-code/coder"
version = "4.4.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
@@ -57,6 +57,9 @@ module "claude-code" {
This example shows how to configure the Claude Code module with an AI prompt, API key shared by all users of the template, and other custom settings.
> [!NOTE]
> When a specific `claude_code_version` (other than "latest") is provided, the module will install Claude Code via npm instead of the official installer. This allows for version pinning. The `claude_binary_path` variable can be used to specify where a pre-installed Claude binary is located.
```tf
data "coder_parameter" "ai_prompt" {
type = "string"
@@ -68,7 +71,7 @@ data "coder_parameter" "ai_prompt" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.3.0"
version = "4.4.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -76,7 +79,8 @@ module "claude-code" {
# OR
claude_code_oauth_token = "xxxxx-xxxx-xxxx"
claude_code_version = "2.0.62" # Pin to a specific version
claude_code_version = "2.0.62" # Pin to a specific version (uses npm)
claude_binary_path = "/opt/claude/bin" # Path to pre-installed Claude binary
agentapi_version = "0.11.4"
ai_prompt = data.coder_parameter.ai_prompt.value
@@ -104,7 +108,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.3.0"
version = "4.4.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
@@ -126,7 +130,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.3.0"
version = "4.4.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -199,7 +203,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.3.0"
version = "4.4.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -256,7 +260,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.3.0"
version = "4.4.0"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
@@ -184,20 +184,15 @@ describe("claude-code", async () => {
test("claude-model", async () => {
const model = "opus";
const { id } = await setup({
const { coderEnvVars } = await setup({
moduleVariables: {
model: model,
ai_prompt: "test prompt",
},
});
await execModuleScript(id);
const startLog = await execContainer(id, [
"bash",
"-c",
"cat /home/coder/.claude-module/agentapi-start.log",
]);
expect(startLog.stdout).toContain(`--model ${model}`);
// Verify ANTHROPIC_MODEL env var is set via coder_env
expect(coderEnvVars["ANTHROPIC_MODEL"]).toBe(model);
});
test("claude-continue-resume-task-session", async () => {
+33 -9
View File
@@ -128,7 +128,7 @@ variable "claude_api_key" {
variable "model" {
type = string
description = "Sets the model for the current session with an alias for the latest model (sonnet or opus) or a models full name."
description = "Sets the default model for Claude Code via ANTHROPIC_MODEL env var. If empty, Claude Code uses its default. Supports aliases (sonnet, opus) or full model names."
default = ""
}
@@ -198,6 +198,18 @@ variable "claude_md_path" {
default = "$HOME/.claude/CLAUDE.md"
}
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"
}
variable "install_via_npm" {
type = bool
description = "Install Claude Code via npm instead of the official installer. Useful if npm is preferred or the official installer fails."
default = false
}
variable "enable_boundary" {
type = bool
description = "Whether to enable coder boundary for network filtering"
@@ -217,8 +229,7 @@ variable "compile_boundary_from_source" {
}
resource "coder_env" "claude_code_md_path" {
count = var.claude_md_path == "" ? 0 : 1
count = var.claude_md_path == "" ? 0 : 1
agent_id = var.agent_id
name = "CODER_MCP_CLAUDE_MD_PATH"
value = var.claude_md_path
@@ -237,16 +248,14 @@ resource "coder_env" "claude_code_oauth_token" {
}
resource "coder_env" "claude_api_key" {
count = length(var.claude_api_key) > 0 ? 1 : 0
count = length(var.claude_api_key) > 0 ? 1 : 0
agent_id = var.agent_id
name = "CLAUDE_API_KEY"
value = var.claude_api_key
}
resource "coder_env" "disable_autoupdater" {
count = var.disable_autoupdater ? 1 : 0
count = var.disable_autoupdater ? 1 : 0
agent_id = var.agent_id
name = "DISABLE_AUTOUPDATER"
value = "1"
@@ -255,7 +264,21 @@ resource "coder_env" "disable_autoupdater" {
resource "coder_env" "claude_binary_path" {
agent_id = var.agent_id
name = "PATH"
value = "$HOME/.local/bin:$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
agent_id = var.agent_id
name = "ANTHROPIC_MODEL"
value = var.model
}
locals {
@@ -328,7 +351,6 @@ module "agentapi" {
echo -n '${base64encode(local.start_script)}' | base64 -d > /tmp/start.sh
chmod +x /tmp/start.sh
ARG_MODEL='${var.model}' \
ARG_RESUME_SESSION_ID='${var.resume_session_id}' \
ARG_CONTINUE='${var.continue}' \
ARG_DANGEROUSLY_SKIP_PERMISSIONS='${var.dangerously_skip_permissions}' \
@@ -353,6 +375,8 @@ module "agentapi" {
ARG_CLAUDE_CODE_VERSION='${var.claude_code_version}' \
ARG_MCP_APP_STATUS_SLUG='${local.app_slug}' \
ARG_INSTALL_CLAUDE_CODE='${var.install_claude_code}' \
ARG_CLAUDE_BINARY_PATH='${var.claude_binary_path}' \
ARG_INSTALL_VIA_NPM='${var.install_via_npm}' \
ARG_REPORT_TASKS='${var.report_tasks}' \
ARG_WORKDIR='${local.workdir}' \
ARG_ALLOWED_TOOLS='${var.allowed_tools}' \
@@ -11,6 +11,8 @@ command_exists() {
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_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:-}
ARG_MCP=$(echo -n "${ARG_MCP:-}" | base64 -d)
@@ -22,6 +24,8 @@ echo "--------------------------------"
printf "ARG_CLAUDE_CODE_VERSION: %s\n" "$ARG_CLAUDE_CODE_VERSION"
printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR"
printf "ARG_INSTALL_CLAUDE_CODE: %s\n" "$ARG_INSTALL_CLAUDE_CODE"
printf "ARG_CLAUDE_BINARY_PATH: %s\n" "$ARG_CLAUDE_BINARY_PATH"
printf "ARG_INSTALL_VIA_NPM: %s\n" "$ARG_INSTALL_VIA_NPM"
printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS"
printf "ARG_MCP_APP_STATUS_SLUG: %s\n" "$ARG_MCP_APP_STATUS_SLUG"
printf "ARG_MCP: %s\n" "$ARG_MCP"
@@ -30,20 +34,66 @@ printf "ARG_DISALLOWED_TOOLS: %s\n" "$ARG_DISALLOWED_TOOLS"
echo "--------------------------------"
function ensure_claude_in_path() {
if [ -z "${CODER_SCRIPT_BIN_DIR:-}" ]; then
echo "CODER_SCRIPT_BIN_DIR not set, skipping PATH setup"
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
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"
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
}
function install_claude_code_cli() {
if [ "$ARG_INSTALL_CLAUDE_CODE" = "true" ]; then
if [ "$ARG_INSTALL_CLAUDE_CODE" != "true" ]; then
echo "Skipping Claude Code installation as per configuration."
ensure_claude_in_path
return
fi
# Use npm when install_via_npm is true or for specific version pinning
if [ "$ARG_INSTALL_VIA_NPM" = "true" ] || { [ -n "$ARG_CLAUDE_CODE_VERSION" ] && [ "$ARG_CLAUDE_CODE_VERSION" != "latest" ]; }; then
echo "Installing Claude Code via npm (version: $ARG_CLAUDE_CODE_VERSION)"
npm install -g "@anthropic-ai/claude-code@$ARG_CLAUDE_CODE_VERSION"
echo "Installed Claude Code via npm. Version: $(claude --version || echo 'unknown')"
else
echo "Installing Claude Code via official installer"
set +e
curl -fsSL claude.ai/install.sh | bash -s -- "$ARG_CLAUDE_CODE_VERSION" 2>&1
CURL_EXIT=${PIPESTATUS[0]}
set -e
if [ $CURL_EXIT -ne 0 ]; then
echo "Claude Code installer failed with exit code $$CURL_EXIT"
echo "Claude Code installer failed with exit code $CURL_EXIT"
fi
echo "Installed Claude Code successfully. Version: $(claude --version || echo 'unknown')"
else
echo "Skipping Claude Code installation as per configuration."
fi
ensure_claude_in_path
}
function setup_claude_configurations() {
@@ -6,7 +6,6 @@ command_exists() {
command -v "$1" > /dev/null 2>&1
}
ARG_MODEL=${ARG_MODEL:-}
ARG_RESUME_SESSION_ID=${ARG_RESUME_SESSION_ID:-}
ARG_CONTINUE=${ARG_CONTINUE:-false}
ARG_DANGEROUSLY_SKIP_PERMISSIONS=${ARG_DANGEROUSLY_SKIP_PERMISSIONS:-}
@@ -21,7 +20,6 @@ ARG_CODER_HOST=${ARG_CODER_HOST:-}
echo "--------------------------------"
printf "ARG_MODEL: %s\n" "$ARG_MODEL"
printf "ARG_RESUME: %s\n" "$ARG_RESUME_SESSION_ID"
printf "ARG_CONTINUE: %s\n" "$ARG_CONTINUE"
printf "ARG_DANGEROUSLY_SKIP_PERMISSIONS: %s\n" "$ARG_DANGEROUSLY_SKIP_PERMISSIONS"
@@ -170,10 +168,6 @@ function start_agentapi() {
mkdir -p "$ARG_WORKDIR"
cd "$ARG_WORKDIR"
if [ -n "$ARG_MODEL" ]; then
ARGS+=(--model "$ARG_MODEL")
fi
if [ -n "$ARG_PERMISSION_MODE" ]; then
ARGS+=(--permission-mode "$ARG_PERMISSION_MODE")
fi