diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index c00045041..273d7e2b4 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.3.0" + version = "4.3.1" agent_id = coder_agent.main.id workdir = "/home/coder/project" claude_api_key = "xxxx-xxxxx-xxxx" @@ -45,7 +45,7 @@ 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" + version = "4.3.1" agent_id = coder_agent.main.id workdir = "/home/coder/project" enable_boundary = true @@ -68,7 +68,7 @@ data "coder_parameter" "ai_prompt" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.3.0" + version = "4.3.1" agent_id = coder_agent.main.id workdir = "/home/coder/project" @@ -104,7 +104,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.3.1" agent_id = coder_agent.main.id workdir = "/home/coder/project" install_claude_code = true @@ -126,7 +126,7 @@ variable "claude_code_oauth_token" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.3.0" + version = "4.3.1" agent_id = coder_agent.main.id workdir = "/home/coder/project" claude_code_oauth_token = var.claude_code_oauth_token @@ -199,7 +199,7 @@ resource "coder_env" "bedrock_api_key" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.3.0" + version = "4.3.1" agent_id = coder_agent.main.id workdir = "/home/coder/project" model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0" @@ -256,7 +256,7 @@ resource "coder_env" "google_application_credentials" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "4.3.0" + version = "4.3.1" agent_id = coder_agent.main.id workdir = "/home/coder/project" model = "claude-sonnet-4@20250514" diff --git a/registry/coder/modules/claude-code/main.test.ts b/registry/coder/modules/claude-code/main.test.ts index c62493cf8..9d0e47079 100644 --- a/registry/coder/modules/claude-code/main.test.ts +++ b/registry/coder/modules/claude-code/main.test.ts @@ -79,391 +79,25 @@ describe("claude-code", async () => { await expectAgentAPIStarted(id); }); - test("install-claude-code-version", async () => { - const version_to_install = "1.0.40"; + test("run-with-boundary", async () => { const { id, coderEnvVars } = await setup({ - skipClaudeMock: true, - moduleVariables: { - install_claude_code: "true", - claude_code_version: version_to_install, - }, - }); - await execModuleScript(id, coderEnvVars); - const resp = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.claude-module/install.log", - ]); - expect(resp.stdout).toContain(version_to_install); - }); - - test("check-latest-claude-code-version-works", async () => { - const { id, coderEnvVars } = await setup({ - skipClaudeMock: true, skipAgentAPIMock: true, - moduleVariables: { - install_claude_code: "true", - }, - }); - await execModuleScript(id, coderEnvVars); - await expectAgentAPIStarted(id); - }); - - test("claude-api-key", async () => { - const apiKey = "test-api-key-123"; - const { id } = await setup({ - moduleVariables: { - claude_api_key: apiKey, - }, - }); - await execModuleScript(id); - - const envCheck = await execContainer(id, [ - "bash", - "-c", - 'env | grep CLAUDE_API_KEY || echo "CLAUDE_API_KEY not found"', - ]); - expect(envCheck.stdout).toContain("CLAUDE_API_KEY"); - }); - - test("claude-mcp-config", async () => { - const mcpConfig = JSON.stringify({ - mcpServers: { - test: { - command: "test-cmd", - type: "stdio", - }, - }, - }); - const { id, coderEnvVars } = await setup({ skipClaudeMock: true, moduleVariables: { - mcp: mcpConfig, + enable_boundary: "true", }, }); await execModuleScript(id, coderEnvVars); - const resp = await readFileContainer(id, "/home/coder/.claude.json"); - expect(resp).toContain("test-cmd"); - }); - - test("claude-task-prompt", async () => { - const prompt = "This is a task prompt for Claude."; - const { id } = await setup({ - moduleVariables: { - ai_prompt: prompt, - }, - }); - await execModuleScript(id); - - const resp = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.claude-module/agentapi-start.log", - ]); - expect(resp.stdout).toContain(prompt); - }); - - test("claude-permission-mode", async () => { - const mode = "plan"; - 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 { id } = 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}`); - }); - - test("claude-continue-resume-task-session", async () => { - const { id } = await setup({ - moduleVariables: { - continue: "true", - report_tasks: "true", - ai_prompt: "test prompt", - }, - }); - - // Create a mock task session file with the hardcoded task session ID - // Note: Claude CLI creates files without "session-" prefix when using --session-id - const taskSessionId = "cd32e253-ca16-4fd3-9825-d837e74ae3c2"; - const sessionDir = `/home/coder/.claude/projects/-home-coder-project`; - await execContainer(id, ["mkdir", "-p", sessionDir]); - await execContainer(id, [ - "bash", - "-c", - `cat > ${sessionDir}/${taskSessionId}.jsonl << 'SESSIONEOF' -{"sessionId":"${taskSessionId}","message":{"content":"Task"},"timestamp":"2020-01-01T10:00:00.000Z"} -{"type":"assistant","message":{"content":"Response"},"timestamp":"2020-01-01T10:00:05.000Z"} -SESSIONEOF`, - ]); - - await execModuleScript(id); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.claude-module/agentapi-start.log", - ]); - expect(startLog.stdout).toContain("--resume"); - expect(startLog.stdout).toContain(taskSessionId); - expect(startLog.stdout).toContain("Resuming task session"); - expect(startLog.stdout).toContain("--dangerously-skip-permissions"); - }); - - test("pre-post-install-scripts", async () => { - const { id } = await setup({ - moduleVariables: { - pre_install_script: "#!/bin/bash\necho 'claude-pre-install-script'", - post_install_script: "#!/bin/bash\necho 'claude-post-install-script'", - }, - }); - await execModuleScript(id); - - const preInstallLog = await readFileContainer( - id, - "/home/coder/.claude-module/pre_install.log", - ); - expect(preInstallLog).toContain("claude-pre-install-script"); - - const postInstallLog = await readFileContainer( - id, - "/home/coder/.claude-module/post_install.log", - ); - expect(postInstallLog).toContain("claude-post-install-script"); - }); - - test("workdir-variable", async () => { - const workdir = "/home/coder/claude-test-folder"; - const { id } = await setup({ - skipClaudeMock: false, - moduleVariables: { - workdir, - }, - }); - await execModuleScript(id); - - const resp = await readFileContainer( - id, - "/home/coder/.claude-module/agentapi-start.log", - ); - expect(resp).toContain(workdir); - }); - - test("coder-mcp-config-created", async () => { - const { id } = await setup({ - moduleVariables: { - install_claude_code: "false", - }, - }); - await execModuleScript(id); - - const installLog = await readFileContainer( - id, - "/home/coder/.claude-module/install.log", - ); - expect(installLog).toContain( - "Configuring Claude Code to report tasks via Coder MCP", - ); - }); - - test("dangerously-skip-permissions", async () => { - const { id } = await setup({ - moduleVariables: { - dangerously_skip_permissions: "true", - }, - }); - await execModuleScript(id); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.claude-module/agentapi-start.log", - ]); - expect(startLog.stdout).toContain(`--dangerously-skip-permissions`); - }); - - test("subdomain-false", async () => { - const { id } = await setup({ - skipAgentAPIMock: true, - moduleVariables: { - subdomain: "false", - post_install_script: dedent` - #!/bin/bash - env | grep AGENTAPI_CHAT_BASE_PATH || echo "AGENTAPI_CHAT_BASE_PATH not found" - `, - }, - }); - - await execModuleScript(id); - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.claude-module/post_install.log", - ]); - expect(startLog.stdout).toContain( - "ARG_AGENTAPI_CHAT_BASE_PATH=/@default/default.foo/apps/ccw/chat", - ); - }); - - test("partial-initialization-detection", async () => { - const { id } = await setup({ - moduleVariables: { - continue: "true", - report_tasks: "true", - ai_prompt: "test prompt", - }, - }); - - const taskSessionId = "cd32e253-ca16-4fd3-9825-d837e74ae3c2"; - const sessionDir = `/home/coder/.claude/projects/-home-coder-project`; - await execContainer(id, ["mkdir", "-p", sessionDir]); - - await execContainer(id, [ - "bash", - "-c", - `echo '{"sessionId":"${taskSessionId}"}' > ${sessionDir}/${taskSessionId}.jsonl`, - ]); - - await execModuleScript(id); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.claude-module/agentapi-start.log", - ]); - - // Should start new session, not try to resume invalid one - expect(startLog.stdout).toContain("Starting new task session"); - expect(startLog.stdout).toContain("--session-id"); - }); - - test("standalone-first-build-no-sessions", async () => { - const { id } = await setup({ - moduleVariables: { - continue: "true", - report_tasks: "false", - }, - }); - - await execModuleScript(id); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.claude-module/agentapi-start.log", - ]); - - // Should start fresh, not try to continue - expect(startLog.stdout).toContain("No sessions found"); - expect(startLog.stdout).toContain("starting fresh standalone session"); - expect(startLog.stdout).not.toContain("--continue"); - }); - - test("standalone-with-sessions-continues", async () => { - const { id } = await setup({ - moduleVariables: { - continue: "true", - report_tasks: "false", - }, - }); - - const sessionDir = `/home/coder/.claude/projects/-home-coder-project`; - await execContainer(id, ["mkdir", "-p", sessionDir]); - await execContainer(id, [ - "bash", - "-c", - `cat > ${sessionDir}/generic-123.jsonl << 'EOF' -{"sessionId":"generic-123","message":{"content":"User session"},"timestamp":"2020-01-01T10:00:00.000Z"} -{"type":"assistant","message":{"content":"Response"},"timestamp":"2020-01-01T10:00:05.000Z"} -EOF`, - ]); - - await execModuleScript(id); + await expectAgentAPIStarted(id); const startLog = await execContainer(id, [ "bash", "-c", "cat /home/coder/.claude-module/agentapi-start.log", ]); - - // Should continue existing session - expect(startLog.stdout).toContain("Sessions found"); expect(startLog.stdout).toContain( - "Continuing most recent standalone session", + `boundary-run wrapper is available in PATH`, ); - expect(startLog.stdout).toContain("--continue"); - }); - - test("task-mode-ignores-manual-sessions", async () => { - const { id } = await setup({ - moduleVariables: { - continue: "true", - report_tasks: "true", - ai_prompt: "test prompt", - }, - }); - - const taskSessionId = "cd32e253-ca16-4fd3-9825-d837e74ae3c2"; - const sessionDir = `/home/coder/.claude/projects/-home-coder-project`; - await execContainer(id, ["mkdir", "-p", sessionDir]); - - // Create task session (without "session-" prefix, as CLI does) - await execContainer(id, [ - "bash", - "-c", - `cat > ${sessionDir}/${taskSessionId}.jsonl << 'EOF' -{"sessionId":"${taskSessionId}","message":{"content":"Task"},"timestamp":"2020-01-01T10:00:00.000Z"} -{"type":"assistant","message":{"content":"Response"},"timestamp":"2020-01-01T10:00:05.000Z"} -EOF`, - ]); - - // Create manual session (newer) - await execContainer(id, [ - "bash", - "-c", - `cat > ${sessionDir}/manual-456.jsonl << 'EOF' -{"sessionId":"manual-456","message":{"content":"Manual"},"timestamp":"2020-01-02T10:00:00.000Z"} -{"type":"assistant","message":{"content":"Response"},"timestamp":"2020-01-02T10:00:05.000Z"} -EOF`, - ]); - - await execModuleScript(id); - - const startLog = await execContainer(id, [ - "bash", - "-c", - "cat /home/coder/.claude-module/agentapi-start.log", - ]); - - // Should resume task session, not manual session - expect(startLog.stdout).toContain("Resuming task session"); - expect(startLog.stdout).toContain(taskSessionId); - expect(startLog.stdout).not.toContain("manual-456"); }); }); diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index 62f24c362..81ba4eaf8 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -206,8 +206,8 @@ variable "enable_boundary" { variable "boundary_version" { type = string - description = "Boundary version, valid git reference should be provided (tag, commit, branch)" - default = "main" + description = "Boundary version, defaults to latest." + default = "latest" } variable "compile_boundary_from_source" { diff --git a/registry/coder/modules/claude-code/scripts/start.sh b/registry/coder/modules/claude-code/scripts/start.sh index e14b26a6f..091593b92 100644 --- a/registry/coder/modules/claude-code/scripts/start.sh +++ b/registry/coder/modules/claude-code/scripts/start.sh @@ -15,7 +15,7 @@ ARG_WORKDIR=${ARG_WORKDIR:-"$HOME"} ARG_AI_PROMPT=$(echo -n "${ARG_AI_PROMPT:-}" | base64 -d) ARG_REPORT_TASKS=${ARG_REPORT_TASKS:-true} ARG_ENABLE_BOUNDARY=${ARG_ENABLE_BOUNDARY:-false} -ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"main"} +ARG_BOUNDARY_VERSION=${ARG_BOUNDARY_VERSION:-"latest"} ARG_COMPILE_FROM_SOURCE=${ARG_COMPILE_FROM_SOURCE:-false} ARG_CODER_HOST=${ARG_CODER_HOST:-}