Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions registry/coder/modules/claude-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.2.1"
version = "3.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
Expand All @@ -32,6 +32,10 @@ module "claude-code" {
- You can get the API key from the [Anthropic Console](https://console.anthropic.com/dashboard).
- You can get the Session Token using the `claude setup-token` command. This is a long-lived authentication token (requires Claude subscription)

### Session Resumption Behavior

By default, Claude Code automatically resumes existing conversations when your workspace restarts. Sessions are tracked per workspace directory, so conversations continue where you left off. If no session exists (first start), your `ai_prompt` will run normally. To disable this behavior and always start fresh, set `continue = false`

## Examples

### Usage with Agent Boundaries
Expand Down Expand Up @@ -66,7 +70,7 @@ data "coder_parameter" "ai_prompt" {

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "3.2.1"
version = "3.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"

Expand Down Expand Up @@ -102,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.2.1"
version = "3.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder"
install_claude_code = true
Expand All @@ -125,7 +129,7 @@ variable "claude_code_oauth_token" {

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "3.2.1"
version = "3.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
Expand Down Expand Up @@ -198,7 +202,7 @@ resource "coder_env" "bedrock_api_key" {

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "3.2.1"
version = "3.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
Expand Down Expand Up @@ -255,7 +259,7 @@ resource "coder_env" "google_application_credentials" {

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "3.2.1"
version = "3.2.2"
agent_id = coder_agent.example.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
Expand Down
23 changes: 18 additions & 5 deletions registry/coder/modules/claude-code/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ describe("claude-code", async () => {
const { id } = await setup({
moduleVariables: {
permission_mode: mode,
task_prompt: "test prompt",
ai_prompt: "test prompt",
},
});
await execModuleScript(id);
Expand All @@ -185,7 +185,7 @@ describe("claude-code", async () => {
const { id } = await setup({
moduleVariables: {
model: model,
task_prompt: "test prompt",
ai_prompt: "test prompt",
},
});
await execModuleScript(id);
Expand All @@ -198,21 +198,34 @@ describe("claude-code", async () => {
expect(startLog.stdout).toContain(`--model ${model}`);
});

test("claude-continue-previous-conversation", async () => {
test("claude-continue-resume-existing-session", async () => {
const { id } = await setup({
moduleVariables: {
continue: "true",
task_prompt: "test prompt",
ai_prompt: "test prompt",
},
});

// Create a mock session file with the predefined 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]);
await execContainer(id, [
"bash",
"-c",
`touch ${sessionDir}/session-${taskSessionId}.jsonl`,
]);

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("--resume");
expect(startLog.stdout).toContain(taskSessionId);
expect(startLog.stdout).toContain("Resuming existing task session");
});

test("pre-post-install-scripts", async () => {
Expand Down
4 changes: 2 additions & 2 deletions registry/coder/modules/claude-code/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ variable "resume_session_id" {

variable "continue" {
type = bool
description = "Load the most recent conversation in the current directory. Task will fail in a new workspace with no conversation/session to continue"
default = false
description = "Automatically continue existing sessions on workspace restart. When true, resumes existing conversation if found, otherwise runs prompt or starts new session. When false, always starts fresh (ignores existing sessions)."
default = true
}

variable "dangerously_skip_permissions" {
Expand Down
70 changes: 51 additions & 19 deletions registry/coder/modules/claude-code/scripts/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,37 +64,70 @@ function validate_claude_installation() {
fi
}

ARGS=()
TASK_SESSION_ID="cd32e253-ca16-4fd3-9825-d837e74ae3c2"

function build_claude_args() {
if [ -n "$ARG_MODEL" ]; then
ARGS+=(--model "$ARG_MODEL")
task_session_exists() {
if find "$HOME/.claude" -type f -name "*${TASK_SESSION_ID}*" 2> /dev/null | grep -q .; then
return 0
else
return 1
fi
}

if [ -n "$ARG_RESUME_SESSION_ID" ]; then
ARGS+=(--resume "$ARG_RESUME_SESSION_ID")
fi
ARGS=()

if [ "$ARG_CONTINUE" = "true" ]; then
ARGS+=(--continue)
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

}

function start_agentapi() {
mkdir -p "$ARG_WORKDIR"
cd "$ARG_WORKDIR"
if [ -n "$ARG_AI_PROMPT" ]; then
ARGS+=(--dangerously-skip-permissions "$ARG_AI_PROMPT")
else
if [ -n "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" ]; then
if [ -n "$ARG_RESUME_SESSION_ID" ]; then
echo "Using explicit resume_session_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
echo "Task session detected (ID: $TASK_SESSION_ID)"
ARGS+=(--resume "$TASK_SESSION_ID")
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
ARGS+=(--dangerously-skip-permissions)
fi
echo "Resuming existing task session"
else
echo "No existing task session found"
ARGS+=(--session-id "$TASK_SESSION_ID")
if [ -n "$ARG_AI_PROMPT" ]; then
ARGS+=(--dangerously-skip-permissions "$ARG_AI_PROMPT")
echo "Starting new task session with prompt"
else
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
ARGS+=(--dangerously-skip-permissions)
fi
echo "Starting new task session"
fi
fi
else
echo "Continue disabled, starting fresh session"
if [ -n "$ARG_AI_PROMPT" ]; then
ARGS+=(--dangerously-skip-permissions "$ARG_AI_PROMPT")
echo "Starting new session with prompt"
else
if [ "$ARG_DANGEROUSLY_SKIP_PERMISSIONS" = "true" ]; then
ARGS+=(--dangerously-skip-permissions)
fi
echo "Starting claude code session"
fi
fi

printf "Running claude code with args: %s\n" "$(printf '%q ' "${ARGS[@]}")"

if [ "${ARG_ENABLE_BOUNDARY:-false}" = "true" ]; then
Expand Down Expand Up @@ -140,5 +173,4 @@ function start_agentapi() {
}

validate_claude_installation
build_claude_args
start_agentapi