Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e45faf4
feat: initial session refactor
DevelopmentCats Dec 3, 2025
76a9b75
refactor(claude-code): update session file naming conventions and imp…
DevelopmentCats Dec 3, 2025
289f3b1
refactor(claude-code): correct session validation logic in start.sh
DevelopmentCats Dec 3, 2025
bbaba7f
chore(claude-code): cleanup README and bump version
DevelopmentCats Dec 3, 2025
ff08d72
refactor(claude-code): improve session validation in tests and scripts
DevelopmentCats Dec 3, 2025
04a160c
fix(tests): update timestamps in session test cases to 2020 for consi…
DevelopmentCats Dec 3, 2025
3e5d65a
fix(claude-code): enhance session validation in start.sh to check for…
DevelopmentCats Dec 3, 2025
e0814cc
Merge branch 'main' into cat/claude-session-refactor
DevelopmentCats Dec 3, 2025
5adfd09
chore: bun fmt
DevelopmentCats Dec 3, 2025
fdb9391
chore: shellcheck
DevelopmentCats Dec 3, 2025
59fb5c0
fix(claude-code): enhance session validation in start.sh to remove st…
DevelopmentCats Dec 4, 2025
bf22ad6
fix(claude-code): ensure AI prompt argument is consistently added in …
DevelopmentCats Dec 5, 2025
247874c
fix(claude-code): update environment variable name from CLAUDE_API_KE…
DevelopmentCats Dec 5, 2025
0e52e07
fix(claude-code): rename environment variable to CLAUDE_API_KEY and a…
DevelopmentCats Dec 5, 2025
0a6660d
fix(claude-code): update environment variable references to CLAUDE_AP…
DevelopmentCats Dec 5, 2025
3903a08
chore: bun fmt
DevelopmentCats Dec 5, 2025
118046b
Merge branch 'main' into cat/claude-session-refactor
DevelopmentCats Dec 5, 2025
2fedb2f
Merge branch 'main' into cat/claude-session-refactor
35C4n0r Dec 8, 2025
516150d
Merge branch 'main' into cat/claude-session-refactor
DevelopmentCats Dec 8, 2025
697a66c
fix: limit search depth for JSONL files in session validation
DevelopmentCats Dec 8, 2025
5ff8af1
refactor: reorder JSONL format validation in session validation logic
DevelopmentCats Dec 8, 2025
404cc69
fix: ensure JSONL files are non-empty in session validation
DevelopmentCats Dec 8, 2025
0aa2847
refactor: extract project directory and session file retrieval into s…
DevelopmentCats Dec 8, 2025
7a68761
Merge branch 'main' into cat/claude-session-refactor
matifali Dec 9, 2025
06b031f
Update agent_id references in README.md
matifali Dec 9, 2025
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
51 changes: 26 additions & 25 deletions registry/coder/modules/claude-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ 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.2.4"
agent_id = coder_agent.example.id
version = "4.2.5"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
}
Expand Down Expand Up @@ -45,13 +45,15 @@ 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.2.5"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
boundary_version = "main"
boundary_log_dir = "/tmp/boundary_logs"
boundary_log_level = "WARN"
boundary_additional_allowed_urls = ["GET *google.com"]
boundary_proxy_port = "8087"
version = "4.2.4"
}
```

Expand All @@ -70,8 +72,8 @@ data "coder_parameter" "ai_prompt" {

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

claude_api_key = "xxxx-xxxxx-xxxx"
Expand Down Expand Up @@ -106,13 +108,12 @@ 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.2.4"
agent_id = coder_agent.example.id
workdir = "/home/coder"
version = "4.2.5"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
claude_code_version = "2.0.62"
report_tasks = false
cli_app = true
}
```

Expand All @@ -129,8 +130,8 @@ variable "claude_code_oauth_token" {

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.4"
agent_id = coder_agent.example.id
version = "4.2.5"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
}
Expand All @@ -146,13 +147,13 @@ Configure Claude Code to use AWS Bedrock for accessing Claude models through you

```tf
resource "coder_env" "bedrock_use" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "CLAUDE_CODE_USE_BEDROCK"
value = "1"
}

resource "coder_env" "aws_region" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "AWS_REGION"
value = "us-east-1" # Choose your preferred region
}
Expand All @@ -174,13 +175,13 @@ variable "aws_secret_access_key" {
}

resource "coder_env" "aws_access_key_id" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "AWS_ACCESS_KEY_ID"
value = var.aws_access_key_id
}

resource "coder_env" "aws_secret_access_key" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "AWS_SECRET_ACCESS_KEY"
value = var.aws_secret_access_key
}
Expand All @@ -195,15 +196,15 @@ variable "aws_bearer_token_bedrock" {
}

resource "coder_env" "bedrock_api_key" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "AWS_BEARER_TOKEN_BEDROCK"
value = var.aws_bearer_token_bedrock
}

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.4"
agent_id = coder_agent.example.id
version = "4.2.5"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
}
Expand All @@ -228,39 +229,39 @@ variable "vertex_sa_json" {
}

resource "coder_env" "vertex_use" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "CLAUDE_CODE_USE_VERTEX"
value = "1"
}

resource "coder_env" "vertex_project_id" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "ANTHROPIC_VERTEX_PROJECT_ID"
value = "your-gcp-project-id"
}

resource "coder_env" "cloud_ml_region" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "CLOUD_ML_REGION"
value = "global"
}

resource "coder_env" "vertex_sa_json" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "VERTEX_SA_JSON"
value = var.vertex_sa_json
}

resource "coder_env" "google_application_credentials" {
agent_id = coder_agent.example.id
agent_id = coder_agent.main.id
name = "GOOGLE_APPLICATION_CREDENTIALS"
value = "/tmp/gcp-sa.json"
}

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.2.4"
agent_id = coder_agent.example.id
version = "4.2.5"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"

Expand Down
180 changes: 142 additions & 38 deletions registry/coder/modules/claude-code/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,17 @@ describe("claude-code", async () => {
});

// 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",
`touch ${sessionDir}/session-${taskSessionId}.jsonl`,
`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);
Expand All @@ -226,46 +230,10 @@ 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("Resuming 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 () => {
const { id } = await setup({
moduleVariables: {
Expand Down Expand Up @@ -360,4 +328,140 @@ describe("claude-code", async () => {
"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);

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",
);
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");
});
});
Loading