Skip to content
Open
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
18 changes: 9 additions & 9 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 = "4.8.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
Expand Down Expand Up @@ -60,7 +60,7 @@ By default, when `enable_boundary = true`, the module uses `coder boundary` subc
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
Expand All @@ -81,7 +81,7 @@ For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_aibridge = true
Expand Down Expand Up @@ -110,7 +110,7 @@ data "coder_task" "me" {}

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
ai_prompt = data.coder_task.me.prompt
Expand All @@ -133,7 +133,7 @@ This example shows additional configuration options for version pinning, custom
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"

Expand Down Expand Up @@ -189,7 +189,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.8.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
Expand All @@ -211,7 +211,7 @@ variable "claude_code_oauth_token" {

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
Expand Down Expand Up @@ -284,7 +284,7 @@ resource "coder_env" "bedrock_api_key" {

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

module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.0"
version = "4.8.1"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
Expand Down
52 changes: 52 additions & 0 deletions registry/coder/modules/claude-code/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,58 @@ EOF`,
expect(startLog.stdout).toContain("--continue");
});

test("standalone-oauth-only-does-not-create-empty-primary-api-key", async () => {
const { id, coderEnvVars } = await setup({
moduleVariables: {
report_tasks: "false",
claude_code_oauth_token: "oauth-token-123",
},
});

await execModuleScript(id, coderEnvVars);

const claudeConfig = JSON.parse(
await readFileContainer(id, "/home/coder/.claude.json"),
);

expect(claudeConfig.primaryApiKey).toBeUndefined();
expect(claudeConfig.hasCompletedOnboarding).toBe(true);
expect(
claudeConfig.projects["/home/coder/project"]
.hasCompletedProjectOnboarding,
).toBe(true);
});

test("standalone-oauth-only-preserves-existing-primary-api-key", async () => {
const existingApiKey = "existing-api-key";
const { id, coderEnvVars } = await setup({
moduleVariables: {
report_tasks: "false",
claude_code_oauth_token: "oauth-token-123",
},
});

await execContainer(id, [
"bash",
"-c",
`cat > /home/coder/.claude.json << 'EOF'
{"primaryApiKey":"${existingApiKey}","projects":{}}
EOF`,
]);

await execModuleScript(id, coderEnvVars);

const claudeConfig = JSON.parse(
await readFileContainer(id, "/home/coder/.claude.json"),
);

expect(claudeConfig.primaryApiKey).toBe(existingApiKey);
expect(claudeConfig.hasCompletedOnboarding).toBe(true);
expect(
claudeConfig.projects["/home/coder/project"].hasTrustDialogAccepted,
).toBe(true);
});

test("task-mode-ignores-manual-sessions", async () => {
const { id } = await setup({
moduleVariables: {
Expand Down
42 changes: 21 additions & 21 deletions registry/coder/modules/claude-code/scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,14 @@ function setup_claude_configurations() {
function configure_standalone_mode() {
echo "Configuring Claude Code for standalone mode..."

if [ -z "${CLAUDE_API_KEY:-}" ] && [ "$ARG_ENABLE_AIBRIDGE" = "false" ]; then
echo "Note: Neither claude_api_key nor enable_aibridge is set, skipping authentication setup"
if [ -z "${CLAUDE_API_KEY:-}" ] \
&& [ -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] \
&& [ "${ARG_ENABLE_AIBRIDGE:-false}" = "false" ]; then
echo "Note: No CLAUDE_API_KEY, no CLAUDE_CODE_OAUTH_TOKEN, and AIBridge disabled — skipping authentication setup"
return
fi

local claude_config="$HOME/.claude.json"
local workdir_normalized
workdir_normalized=$(echo "$ARG_WORKDIR" | tr '/' '-')

# Create or update .claude.json with minimal configuration for API key auth
# This skips the interactive login prompt and onboarding screens
Expand All @@ -198,27 +198,27 @@ function configure_standalone_mode() {
.bypassPermissionsModeAccepted = true |
.hasAcknowledgedCostThreshold = true |
.hasCompletedOnboarding = true |
.primaryApiKey = $apikey |
.projects[$workdir].hasCompletedProjectOnboarding = true |
.projects[$workdir].hasTrustDialogAccepted = true' \
.projects[$workdir].hasTrustDialogAccepted = true |
if $apikey != "" then .primaryApiKey = $apikey else . end' \
"$claude_config" > "${claude_config}.tmp" && mv "${claude_config}.tmp" "$claude_config"
else
echo "Creating new Claude configuration at $claude_config"
cat > "$claude_config" << EOF
{
"autoUpdaterStatus": "disabled",
"bypassPermissionsModeAccepted": true,
"hasAcknowledgedCostThreshold": true,
"hasCompletedOnboarding": true,
"primaryApiKey": "${CLAUDE_API_KEY:-}",
"projects": {
"$ARG_WORKDIR": {
"hasCompletedProjectOnboarding": true,
"hasTrustDialogAccepted": true
}
}
}
EOF
jq -n --arg workdir "$ARG_WORKDIR" --arg apikey "${CLAUDE_API_KEY:-}" \
'{
autoUpdaterStatus: "disabled",
bypassPermissionsModeAccepted: true,
hasAcknowledgedCostThreshold: true,
hasCompletedOnboarding: true,
projects: {
($workdir): {
hasCompletedProjectOnboarding: true,
hasTrustDialogAccepted: true
}
}
} |
if $apikey != "" then . + {primaryApiKey: $apikey} else . end' \
> "$claude_config"
fi

echo "Standalone mode configured successfully"
Expand Down
Loading