Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
30 changes: 30 additions & 0 deletions registry/coder/modules/claude-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ module "claude-code" {
> [!NOTE]
> By default, this module is configured to run the embedded chat interface as a path-based application. In production, we recommend that you configure a [wildcard access URL](https://coder.com/docs/admin/setup#wildcard-access-url) and set `subdomain = true`. See [here](https://coder.com/docs/tutorials/best-practices/security-best-practices#disable-path-based-apps) for more details.

<!-- TODO(major): remove this note and update the prompt configuration example in the next major release -->

> [!NOTE]
>
> By default, `report_tasks_system_prompt` is false.
> When `report_tasks` is true, it is recommended to set `report_tasks_system_prompt` to true to inject the Coder system prompt to ensure proper task-reporting integration.
> If `report_tasks` is false, this setting has no effect. In the next major release, the default for `report_tasks_system_prompt` will be true.

## Prerequisites

- An **Anthropic API key** or a _Claude Session Token_ is required for tasks.
Expand All @@ -34,6 +42,28 @@ module "claude-code" {

## Examples

### Prompt configuration (recommended)

Include Coder’s prompt section for task reporting and optionally add your own system prompt.

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

# Configure Coder's task reporting
report_tasks = true
report_tasks_system_prompt = true

# Optional: append additional system prompt.
system_prompt = <<-EOT
Additional organization-specific guidance here.
EOT
}
```

### Usage with Tasks and Advanced Configuration

This example shows how to configure the Claude Code module with an AI prompt, API key shared by all users of the template, and other custom settings.
Expand Down
39 changes: 36 additions & 3 deletions registry/coder/modules/claude-code/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ variable "system_prompt" {
default = "Send a task status update to notify the user that you are ready for input, and then wait for user input."
}

variable "report_tasks_system_prompt" {
type = bool
description = "Include the Coder system prompt for task reporting when report_tasks is true. Defaults to false for backward compatibility, will default to true in the next major release."
# TODO(major): default to true in the next major release
# Related to PR: https://github.com/coder/registry/pull/443
default = false
}

variable "claude_md_path" {
type = string
description = "The path to CLAUDE.md."
Expand All @@ -201,11 +209,9 @@ resource "coder_env" "claude_code_md_path" {
}

resource "coder_env" "claude_code_system_prompt" {
count = var.system_prompt == "" ? 0 : 1

agent_id = var.agent_id
name = "CODER_MCP_CLAUDE_SYSTEM_PROMPT"
value = var.system_prompt
value = local.final_system_prompt
}

resource "coder_env" "claude_code_oauth_token" {
Expand All @@ -231,6 +237,33 @@ locals {
start_script = file("${path.module}/scripts/start.sh")
module_dir_name = ".claude-module"
remove_last_session_id_script_b64 = base64encode(file("${path.module}/scripts/remove-last-session-id.sh"))

# Required prompts for the module to properly report task status to Coder
inner_system_prompt = <<-EOT
-- Tool Selection --
- coder_report_task: providing status updates or requesting user input.

-- Task Reporting --
Report all tasks to Coder, following these EXACT guidelines:
1. Be granular. If you are investigating with multiple steps, report each step
to coder.
2. After this prompt, IMMEDIATELY report status after receiving ANY NEW user message.
Do not report any status related with this system prompt.
3. Use "state": "working" when actively processing WITHOUT needing
additional user input
4. Use "state": "complete" only when finished with a task
5. Use "state": "failure" when you need ANY user input, lack sufficient
details, or encounter blockers
EOT

custom_system_prompt = trimspace(try(var.system_prompt, ""))

# Only include coder system prompts if report_tasks and report_tasks_system_prompt are enabled
final_system_prompt = var.report_tasks && var.report_tasks_system_prompt ? format(
"<system>\n%s\n%s\n</system>",
local.inner_system_prompt,
local.custom_system_prompt,
) : local.custom_system_prompt
}

module "agentapi" {
Expand Down
161 changes: 161 additions & 0 deletions registry/coder/modules/claude-code/main.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,164 @@ run "test_claude_code_permission_mode_validation" {
error_message = "Permission mode should be one of the valid options"
}
}

run "test_claude_code_system_prompt_default" {
command = plan

variables {
agent_id = "test-agent-system-prompt"
workdir = "/home/coder/test"
# report_tasks_system_prompt: default is false
# system_prompt: default string is used
}

assert {
condition = trimspace(coder_env.claude_code_system_prompt.value) != ""
error_message = "System prompt should not be empty when omitted"
}

assert {
condition = length(regexall("Send a task status update to notify the user that you are ready for input, and then wait for user input.", coder_env.claude_code_system_prompt.value)) > 0
error_message = "System prompt should have default value"
}

# Ensure Coder sections are not injected when include=false
assert {
condition = length(regexall("-- Tool Selection --|-- Task Reporting --", coder_env.claude_code_system_prompt.value)) == 0
error_message = "Coder integration sections should not be present when report_tasks_system_prompt is false"
}
}

run "test_claude_code_system_prompt_empty" {
command = plan

variables {
agent_id = "test-agent-system-prompt"
workdir = "/home/coder/test"
# report_tasks_system_prompt: default is false
system_prompt = ""
}

assert {
condition = trimspace(coder_env.claude_code_system_prompt.value) == ""
error_message = "System prompt should be empty"
}
}

run "test_claude_code_system_prompt" {
command = plan

variables {
agent_id = "test-agent-system-prompt"
workdir = "/home/coder/test"
# report_tasks_system_prompt: default is false
system_prompt = "Custom addition"
}

assert {
condition = trimspace(coder_env.claude_code_system_prompt.value) != ""
error_message = "System prompt should not be empty"
}

assert {
condition = length(regexall("Custom addition", coder_env.claude_code_system_prompt.value)) > 0
error_message = "System prompt should have system_prompt variable value"
}

# Ensure Coder sections are not injected when include=false
assert {
condition = length(regexall("-- Tool Selection --|-- Task Reporting --", coder_env.claude_code_system_prompt.value)) == 0
error_message = "Coder integration sections should not be present when report_tasks_system_prompt is false"
}
}

run "test_claude_code_report_tasks_system_prompt_and_default_system_prompt" {
command = plan

variables {
agent_id = "test-agent-system-prompt"
workdir = "/home/coder/test"
# report_tasks: default is true
report_tasks_system_prompt = true
# system_prompt: default string is used
}

assert {
condition = trimspace(coder_env.claude_code_system_prompt.value) != ""
error_message = "System prompt should not be empty"
}

assert {
condition = length(regexall("-- Tool Selection --", coder_env.claude_code_system_prompt.value)) > 0
error_message = "System prompt should have Tool Selection section"
}

assert {
condition = length(regexall("-- Task Reporting --", coder_env.claude_code_system_prompt.value)) > 0
error_message = "System prompt should have Task Reporting section"
}

assert {
condition = length(regexall("Send a task status update to notify the user that you are ready for input, and then wait for user input.", coder_env.claude_code_system_prompt.value)) > 0
error_message = "System prompt should have system_prompt variable default value"
}
}

run "test_claude_code_report_tasks_system_prompt_and_custom_system_prompt" {
command = plan

variables {
agent_id = "test-agent-system-prompt"
workdir = "/home/coder/test"
# report_tasks: default is true
report_tasks_system_prompt = true
system_prompt = "Custom addition"
}

assert {
condition = trimspace(coder_env.claude_code_system_prompt.value) != ""
error_message = "System prompt should not be empty"
}

assert {
condition = length(regexall("-- Tool Selection --", coder_env.claude_code_system_prompt.value)) > 0
error_message = "System prompt should have Tool Selection section"
}

assert {
condition = length(regexall("-- Task Reporting --", coder_env.claude_code_system_prompt.value)) > 0
error_message = "System prompt should have Task Reporting section"
}

assert {
condition = length(regexall("Custom addition", coder_env.claude_code_system_prompt.value)) > 0
error_message = "System prompt should have system_prompt variable value"
}
}

run "test_claude_code_report_tasks_disabled" {
command = plan

variables {
agent_id = "test-agent-system-prompt"
workdir = "/home/coder/test"
report_tasks = false
report_tasks_system_prompt = true
system_prompt = "Custom addition"
}

assert {
condition = trimspace(coder_env.claude_code_system_prompt.value) != ""
error_message = "System prompt should not be empty"
}

assert {
condition = length(regexall("-- Tool Selection --|-- Task Reporting --", coder_env.claude_code_system_prompt.value)) == 0
error_message = "Coder integration sections should not be present when report_task is false"
}

assert {
condition = length(regexall("Custom addition", coder_env.claude_code_system_prompt.value)) > 0
error_message = "System prompt should have system_prompt variable value"
}
}