diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index ea845f6ec..3adfe208f 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.9.1" + version = "4.9.2" agent_id = coder_agent.main.id workdir = "/home/coder/project" claude_api_key = "xxxx-xxxxx-xxxx" diff --git a/registry/coder/modules/claude-code/main.test.ts b/registry/coder/modules/claude-code/main.test.ts index 19ab98c04..b01e88327 100644 --- a/registry/coder/modules/claude-code/main.test.ts +++ b/registry/coder/modules/claude-code/main.test.ts @@ -182,6 +182,24 @@ describe("claude-code", async () => { expect(startLog.stdout).toContain(`--permission-mode ${mode}`); }); + test("claude-auto-permission-mode", async () => { + const mode = "auto"; + const { id } = await setup({ + moduleVariables: { + permission_mode: mode, + 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(`--permission-mode ${mode}`); + }); + test("claude-model", async () => { const model = "opus"; const { coderEnvVars } = await setup({ diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index 7a08aa82f..db234c052 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -161,8 +161,8 @@ variable "permission_mode" { description = "Permission mode for the cli, check https://docs.anthropic.com/en/docs/claude-code/iam#permission-modes" default = "" validation { - condition = contains(["", "default", "acceptEdits", "plan", "bypassPermissions"], var.permission_mode) - error_message = "interaction_mode must be one of: default, acceptEdits, plan, bypassPermissions." + condition = contains(["", "default", "acceptEdits", "plan", "auto", "bypassPermissions"], var.permission_mode) + error_message = "interaction_mode must be one of: default, acceptEdits, plan, auto, bypassPermissions." } } @@ -430,6 +430,7 @@ module "agentapi" { ARG_MCP='${var.mcp != null ? base64encode(replace(var.mcp, "'", "'\\''")) : ""}' \ ARG_MCP_CONFIG_REMOTE_PATH='${base64encode(jsonencode(var.mcp_config_remote_path))}' \ ARG_ENABLE_AIBRIDGE='${var.enable_aibridge}' \ + ARG_PERMISSION_MODE='${var.permission_mode}' \ /tmp/install.sh EOT } diff --git a/registry/coder/modules/claude-code/main.tftest.hcl b/registry/coder/modules/claude-code/main.tftest.hcl index 66c79bab7..9c9df50f4 100644 --- a/registry/coder/modules/claude-code/main.tftest.hcl +++ b/registry/coder/modules/claude-code/main.tftest.hcl @@ -183,11 +183,26 @@ run "test_claude_code_permission_mode_validation" { } assert { - condition = contains(["", "default", "acceptEdits", "plan", "bypassPermissions"], var.permission_mode) + condition = contains(["", "default", "acceptEdits", "plan", "auto", "bypassPermissions"], var.permission_mode) error_message = "Permission mode should be one of the valid options" } } +run "test_claude_code_auto_permission_mode" { + command = plan + + variables { + agent_id = "test-agent-auto" + workdir = "/home/coder/test" + permission_mode = "auto" + } + + assert { + condition = var.permission_mode == "auto" + error_message = "Permission mode should be set to auto" + } +} + run "test_claude_code_with_boundary" { command = plan diff --git a/registry/coder/modules/claude-code/scripts/install.sh b/registry/coder/modules/claude-code/scripts/install.sh index 0a2ba7033..c00773b5e 100644 --- a/registry/coder/modules/claude-code/scripts/install.sh +++ b/registry/coder/modules/claude-code/scripts/install.sh @@ -22,6 +22,7 @@ ARG_MCP_CONFIG_REMOTE_PATH=$(echo -n "${ARG_MCP_CONFIG_REMOTE_PATH:-}" | base64 ARG_ALLOWED_TOOLS=${ARG_ALLOWED_TOOLS:-} ARG_DISALLOWED_TOOLS=${ARG_DISALLOWED_TOOLS:-} ARG_ENABLE_AIBRIDGE=${ARG_ENABLE_AIBRIDGE:-false} +ARG_PERMISSION_MODE=${ARG_PERMISSION_MODE:-} export PATH="$ARG_CLAUDE_BINARY_PATH:$PATH" @@ -195,6 +196,7 @@ function configure_standalone_mode() { jq --arg workdir "$ARG_WORKDIR" --arg apikey "${CLAUDE_API_KEY:-}" \ '.autoUpdaterStatus = "disabled" | + .autoModeAccepted = true | .bypassPermissionsModeAccepted = true | .hasAcknowledgedCostThreshold = true | .hasCompletedOnboarding = true | @@ -207,6 +209,7 @@ function configure_standalone_mode() { cat > "$claude_config" << EOF { "autoUpdaterStatus": "disabled", + "autoModeAccepted": true, "bypassPermissionsModeAccepted": true, "hasAcknowledgedCostThreshold": true, "hasCompletedOnboarding": true, @@ -235,6 +238,28 @@ function report_tasks() { fi } +function accept_auto_mode() { + # Pre-accept the auto mode TOS prompt so it doesn't appear interactively. + # Claude Code shows a confirmation dialog for auto mode that blocks + # non-interactive/headless usage. + # Note: bypassPermissions acceptance is already handled by + # coder exp mcp configure (task mode) and configure_standalone_mode. + local claude_config="$HOME/.claude.json" + + if [ -f "$claude_config" ]; then + jq '.autoModeAccepted = true' \ + "$claude_config" > "${claude_config}.tmp" && mv "${claude_config}.tmp" "$claude_config" + else + echo '{"autoModeAccepted": true}' > "$claude_config" + fi + + echo "Pre-accepted auto mode prompt" +} + install_claude_code_cli setup_claude_configurations report_tasks + +if [ "$ARG_PERMISSION_MODE" = "auto" ]; then + accept_auto_mode +fi