Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
26 changes: 26 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,12 @@ 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, `include_coder_system_prompt` is false. For proper integration with Coder (tool selection & task reporting),
> it is recommended to set `include_coder_system_prompt` to true. In the next major release, this will become the default.

## Prerequisites

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

## Examples

### Prompt configuration (recommended)

Include Coder’s prompt sections 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"

include_coder_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
47 changes: 44 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,13 @@ 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 "include_coder_system_prompt" {
type = bool
description = "Include Coder system prompts for proper integration with tool selection and task reporting. 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
default = false
}

variable "claude_md_path" {
type = string
description = "The path to CLAUDE.md."
Expand All @@ -201,11 +208,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 +236,42 @@ 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 integrate with Coder
inner_system_prompt = <<-EOT
-- Tool Selection --
- coder_report_task: providing status updates or requesting user input.
- playwright: previewing your changes after you made them
to confirm it worked as expected
- desktop-commander - use only for commands that keep running
(servers, dev watchers, GUI apps).
- Built-in tools - use for everything else:
(file operations, git commands, builds & installs, one-off shell commands)

Remember this decision rule:
- Stays running? → desktop-commander
- Finishes immediately? → built-in tools

-- 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, ""))

final_system_prompt = var.include_coder_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
132 changes: 132 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,135 @@ 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"
# system_prompt: default string is used
# include_coder_system_prompt: default is false
}

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 include_coder_system_prompt is false"
}
}

run "test_claude_code_system_prompt_empty" {
command = plan

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

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"
system_prompt = "Custom addition"
# include_coder_system_prompt: default is false
}

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 include_coder_system_prompt is false"
}
}

run "test_claude_code_include_coder_system_prompt_and_default_system_prompt" {
command = plan

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

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_include_coder_system_prompt_and_custom_system_prompt" {
command = plan

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

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"
}
}