diff --git a/registry/coder/modules/claude-code/README.md b/registry/coder/modules/claude-code/README.md index 8e26416e2..87f9a4dfe 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 = "3.4.4" + version = "3.4.5" agent_id = coder_agent.example.id workdir = "/home/coder/project" claude_api_key = "xxxx-xxxxx-xxxx" @@ -70,7 +70,7 @@ data "coder_parameter" "ai_prompt" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "3.4.4" + version = "3.4.5" agent_id = coder_agent.example.id workdir = "/home/coder/project" @@ -106,7 +106,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 = "3.4.4" + version = "3.4.5" agent_id = coder_agent.example.id workdir = "/home/coder" install_claude_code = true @@ -129,7 +129,7 @@ variable "claude_code_oauth_token" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "3.4.4" + version = "3.4.5" agent_id = coder_agent.example.id workdir = "/home/coder/project" claude_code_oauth_token = var.claude_code_oauth_token @@ -202,7 +202,7 @@ resource "coder_env" "bedrock_api_key" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "3.4.4" + version = "3.4.5" agent_id = coder_agent.example.id workdir = "/home/coder/project" model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0" @@ -259,7 +259,7 @@ resource "coder_env" "google_application_credentials" { module "claude-code" { source = "registry.coder.com/coder/claude-code/coder" - version = "3.4.4" + version = "3.4.5" agent_id = coder_agent.example.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 a7c2dd14c..94fcb391a 100644 --- a/registry/coder/modules/claude-code/main.test.ts +++ b/registry/coder/modules/claude-code/main.test.ts @@ -198,15 +198,16 @@ describe("claude-code", async () => { expect(startLog.stdout).toContain(`--model ${model}`); }); - test("claude-continue-resume-existing-session", async () => { + test("claude-continue-resume-task-session", async () => { const { id } = await setup({ moduleVariables: { continue: "true", + report_tasks: "true", ai_prompt: "test prompt", }, }); - // Create a mock session file with the predefined task session ID + // Create a mock task session file with the hardcoded task session ID const taskSessionId = "cd32e253-ca16-4fd3-9825-d837e74ae3c2"; const sessionDir = `/home/coder/.claude/projects/-home-coder-project`; await execContainer(id, ["mkdir", "-p", sessionDir]); @@ -226,6 +227,43 @@ describe("claude-code", async () => { expect(startLog.stdout).toContain("--resume"); expect(startLog.stdout).toContain(taskSessionId); expect(startLog.stdout).toContain("Resuming existing task session"); + expect(startLog.stdout).toContain("--dangerously-skip-permissions"); + }); + + test("claude-continue-resume-standalone-session", async () => { + const { id } = await setup({ + moduleVariables: { + continue: "true", + report_tasks: "false", + ai_prompt: "test prompt", + }, + }); + + const sessionId = "some-random-session-id"; + const workdir = "/home/coder/project"; + const claudeJson = { + projects: { + [workdir]: { + lastSessionId: sessionId, + }, + }, + }; + + await execContainer(id, [ + "bash", + "-c", + `echo '${JSON.stringify(claudeJson)}' > /home/coder/.claude.json`, + ]); + + await execModuleScript(id); + + const startLog = await execContainer(id, [ + "bash", + "-c", + "cat /home/coder/.claude-module/agentapi-start.log", + ]); + expect(startLog.stdout).toContain("--continue"); + expect(startLog.stdout).toContain("Resuming existing session"); }); test("pre-post-install-scripts", async () => { diff --git a/registry/coder/modules/claude-code/main.tf b/registry/coder/modules/claude-code/main.tf index 93b3761b6..bf08af033 100644 --- a/registry/coder/modules/claude-code/main.tf +++ b/registry/coder/modules/claude-code/main.tf @@ -349,6 +349,7 @@ module "agentapi" { 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_BOUNDARY_LOG_DIR='${var.boundary_log_dir}' \ diff --git a/registry/coder/modules/claude-code/main.tftest.hcl b/registry/coder/modules/claude-code/main.tftest.hcl index 6994caf24..adfca6d20 100644 --- a/registry/coder/modules/claude-code/main.tftest.hcl +++ b/registry/coder/modules/claude-code/main.tftest.hcl @@ -57,7 +57,7 @@ run "test_claude_code_with_custom_options" { group = "development" icon = "/icon/custom.svg" model = "opus" - task_prompt = "Help me write better code" + ai_prompt = "Help me write better code" permission_mode = "plan" continue = true install_claude_code = false @@ -88,8 +88,8 @@ run "test_claude_code_with_custom_options" { } assert { - condition = var.task_prompt == "Help me write better code" - error_message = "Task prompt variable should be set correctly" + condition = var.ai_prompt == "Help me write better code" + error_message = "AI prompt variable should be set correctly" } assert { diff --git a/registry/coder/modules/claude-code/scripts/remove-last-session-id.sh b/registry/coder/modules/claude-code/scripts/remove-last-session-id.sh index dac86a03c..d72369fa7 100755 --- a/registry/coder/modules/claude-code/scripts/remove-last-session-id.sh +++ b/registry/coder/modules/claude-code/scripts/remove-last-session-id.sh @@ -26,15 +26,19 @@ echo ".claude.json path $claude_json_path" # Check if .claude.json exists if [ ! -f "$claude_json_path" ]; then echo "No .claude.json file found" - exit 0 + exit 1 fi # Use jq to check if lastSessionId exists for the working directory and remove it if jq -e ".projects[\"$working_dir\"].lastSessionId" "$claude_json_path" > /dev/null 2>&1; then # Remove lastSessionId and update the file - jq "del(.projects[\"$working_dir\"].lastSessionId)" "$claude_json_path" > "${claude_json_path}.tmp" && mv "${claude_json_path}.tmp" "$claude_json_path" - echo "Removed lastSessionId from .claude.json" + if jq "del(.projects[\"$working_dir\"].lastSessionId)" "$claude_json_path" > "${claude_json_path}.tmp" && mv "${claude_json_path}.tmp" "$claude_json_path"; then + echo "Removed lastSessionId from .claude.json" + exit 0 + else + echo "Failed to remove lastSessionId from .claude.json" + fi else echo "No lastSessionId found in .claude.json - nothing to do" fi diff --git a/registry/coder/modules/claude-code/scripts/start.sh b/registry/coder/modules/claude-code/scripts/start.sh index f5527b4fd..40b164563 100644 --- a/registry/coder/modules/claude-code/scripts/start.sh +++ b/registry/coder/modules/claude-code/scripts/start.sh @@ -20,6 +20,7 @@ ARG_DANGEROUSLY_SKIP_PERMISSIONS=${ARG_DANGEROUSLY_SKIP_PERMISSIONS:-} ARG_PERMISSION_MODE=${ARG_PERMISSION_MODE:-} 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_LOG_DIR=${ARG_BOUNDARY_LOG_DIR:-"/tmp/boundary_logs"} @@ -38,6 +39,7 @@ printf "ARG_DANGEROUSLY_SKIP_PERMISSIONS: %s\n" "$ARG_DANGEROUSLY_SKIP_PERMISSIO printf "ARG_PERMISSION_MODE: %s\n" "$ARG_PERMISSION_MODE" printf "ARG_AI_PROMPT: %s\n" "$ARG_AI_PROMPT" printf "ARG_WORKDIR: %s\n" "$ARG_WORKDIR" +printf "ARG_REPORT_TASKS: %s\n" "$ARG_REPORT_TASKS" printf "ARG_ENABLE_BOUNDARY: %s\n" "$ARG_ENABLE_BOUNDARY" printf "ARG_BOUNDARY_VERSION: %s\n" "$ARG_BOUNDARY_VERSION" printf "ARG_BOUNDARY_LOG_DIR: %s\n" "$ARG_BOUNDARY_LOG_DIR" @@ -47,10 +49,18 @@ printf "ARG_CODER_HOST: %s\n" "$ARG_CODER_HOST" echo "--------------------------------" -# see the remove-last-session-id.sh script for details -# about why we need it -# avoid exiting if the script fails -bash "/tmp/remove-last-session-id.sh" "$(pwd)" 2> /dev/null || true +# Clean up stale session data (see remove-last-session-id.sh for details) +CAN_CONTINUE_CONVERSATION=false +set +e +bash "/tmp/remove-last-session-id.sh" "$(pwd)" 2> /dev/null +session_cleanup_exit_code=$? +set -e + +case $session_cleanup_exit_code in + 0) + CAN_CONTINUE_CONVERSATION=true + ;; +esac function install_boundary() { # Install boundary from public github repo @@ -69,10 +79,15 @@ function validate_claude_installation() { fi } +# Hardcoded task session ID for Coder task reporting +# This ensures all task sessions use a consistent, predictable ID TASK_SESSION_ID="cd32e253-ca16-4fd3-9825-d837e74ae3c2" task_session_exists() { - if find "$HOME/.claude" -type f -name "*${TASK_SESSION_ID}*" 2> /dev/null | grep -q .; then + local workdir_normalized=$(echo "$ARG_WORKDIR" | tr '/' '-') + local project_dir="$HOME/.claude/projects/${workdir_normalized}" + + if [ -d "$project_dir" ] && find "$project_dir" -type f -name "*${TASK_SESSION_ID}*" 2> /dev/null | grep -q .; then return 0 else return 1 @@ -97,39 +112,63 @@ function start_agentapi() { fi if [ -n "$ARG_RESUME_SESSION_ID" ]; then - echo "Using explicit resume_session_id: $ARG_RESUME_SESSION_ID" + echo "Resuming task session by ID: $ARG_RESUME_SESSION_ID" ARGS+=(--resume "$ARG_RESUME_SESSION_ID") if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then ARGS+=(--dangerously-skip-permissions) fi elif [ "$ARG_CONTINUE" = "true" ]; then - if task_session_exists; then + if [ "$ARG_REPORT_TASKS" = "true" ] && task_session_exists; then echo "Task session detected (ID: $TASK_SESSION_ID)" ARGS+=(--resume "$TASK_SESSION_ID") + ARGS+=(--dangerously-skip-permissions) + echo "Resuming existing task session" + elif [ "$ARG_REPORT_TASKS" = "false" ] && [ "$CAN_CONTINUE_CONVERSATION" = true ]; then + echo "Previous session exists" + ARGS+=(--continue) if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then ARGS+=(--dangerously-skip-permissions) fi - echo "Resuming existing task session" + echo "Resuming existing session" else - echo "No existing task session found" - ARGS+=(--session-id "$TASK_SESSION_ID") + echo "No existing session found" + if [ "$ARG_REPORT_TASKS" = "true" ]; then + ARGS+=(--session-id "$TASK_SESSION_ID") + fi if [ -n "$ARG_AI_PROMPT" ]; then - ARGS+=(--dangerously-skip-permissions "$ARG_AI_PROMPT") - echo "Starting new task session with prompt" + if [ "$ARG_REPORT_TASKS" = "true" ]; then + ARGS+=(--dangerously-skip-permissions "$ARG_AI_PROMPT") + else + if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then + ARGS+=(--dangerously-skip-permissions) + fi + ARGS+=("$ARG_AI_PROMPT") + fi + echo "Starting new session with prompt" else - if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then + if [ "$ARG_REPORT_TASKS" = "true" ] || [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then ARGS+=(--dangerously-skip-permissions) fi - echo "Starting new task session" + echo "Starting new session" fi fi else echo "Continue disabled, starting fresh session" + if [ "$ARG_REPORT_TASKS" = "true" ]; then + ARGS+=(--session-id "$TASK_SESSION_ID") + fi if [ -n "$ARG_AI_PROMPT" ]; then - ARGS+=(--dangerously-skip-permissions "$ARG_AI_PROMPT") + if [ "$ARG_REPORT_TASKS" = "true" ]; then + ARGS+=(--dangerously-skip-permissions "$ARG_AI_PROMPT") + else + if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then + ARGS+=(--dangerously-skip-permissions) + fi + ARGS+=("$ARG_AI_PROMPT") + fi echo "Starting new session with prompt" else - if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then + if [ "$ARG_REPORT_TASKS" = "true" ] || [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then ARGS+=(--dangerously-skip-permissions) fi echo "Starting claude code session"