fix(registry/modules/claude-code): default subdomain to false (#419)

Relates to https://github.com/coder/coder/issues/18779

By default, we set `subdomain = true`. Most folks testing this out don't
have a wildcard subdomain setup. This switches to path-based behaviour
by default and adds a note to the troubleshooting section.
This commit is contained in:
Cian Johnston 2025-09-15 13:09:12 +01:00 committed by GitHub
parent 213aabb3b0
commit cb990bbee0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 46 additions and 9 deletions

View File

@ -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 = "2.2.0"
version = "2.2.1"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
@ -26,6 +26,9 @@ module "claude-code" {
> this enables more functionality, it also means Claude Code can potentially execute commands with the same privileges as
> the user running it. Use this module _only_ in trusted environments and be aware of the security implications.
> [!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.
## Prerequisites
- You must add the [Coder Login](https://registry.coder.com/modules/coder-login) module to your template
@ -83,7 +86,7 @@ resource "coder_agent" "main" {
module "claude-code" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/claude-code/coder"
version = "2.2.0"
version = "2.2.1"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true
@ -101,7 +104,7 @@ Run Claude Code as a standalone app in your workspace. This will install Claude
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "2.2.0"
version = "2.2.1"
agent_id = coder_agent.example.id
folder = "/home/coder"
install_claude_code = true

View File

@ -3,6 +3,7 @@ import {
afterEach,
expect,
describe,
it,
setDefaultTimeout,
beforeAll,
} from "bun:test";
@ -100,6 +101,7 @@ const writeAgentAPIMockControl = async ({
interface SetupProps {
skipAgentAPIMock?: boolean;
skipClaudeMock?: boolean;
extraVars?: Record<string, string>;
}
const projectDir = "/home/coder/project";
@ -112,6 +114,7 @@ const setup = async (props?: SetupProps): Promise<{ id: string }> => {
install_claude_code: "false",
agentapi_version: "preview",
folder: projectDir,
...props?.extraVars,
},
});
await execContainer(id, ["bash", "-c", `mkdir -p '${projectDir}'`]);
@ -335,6 +338,36 @@ describe("claude-code", async () => {
id,
"/home/coder/agentapi-mock.log",
);
expect(agentApiStartLog).toContain("AGENTAPI_ALLOWED_HOSTS: *");
expect(agentApiStartLog).toContain("AGENTAPI_ALLOWED_HOSTS=*");
});
describe("subdomain", async () => {
it("sets AGENTAPI_CHAT_BASE_PATH when false", async () => {
const { id } = await setup();
const respModuleScript = await execModuleScript(id);
expect(respModuleScript.exitCode).toBe(0);
await expectAgentAPIStarted(id);
const agentApiStartLog = await readFileContainer(
id,
"/home/coder/agentapi-mock.log",
);
expect(agentApiStartLog).toContain(
"AGENTAPI_CHAT_BASE_PATH=/@default/default.foo/apps/ccw/chat",
);
});
it("does not set AGENTAPI_CHAT_BASE_PATH when true", async () => {
const { id } = await setup({
extraVars: { subdomain: "true" },
});
const respModuleScript = await execModuleScript(id);
expect(respModuleScript.exitCode).toBe(0);
await expectAgentAPIStarted(id);
const agentApiStartLog = await readFileContainer(
id,
"/home/coder/agentapi-mock.log",
);
expect(agentApiStartLog).toMatch(/AGENTAPI_CHAT_BASE_PATH=$/m);
});
});
});

View File

@ -106,12 +106,12 @@ variable "agentapi_version" {
variable "subdomain" {
type = bool
description = "Whether to use a subdomain for the Claude Code app."
default = true
default = false
}
locals {
# we have to trim the slash because otherwise coder exp mcp will
# set up an invalid claude config
# set up an invalid claude config
workdir = trimsuffix(var.folder, "/")
encoded_pre_install_script = var.experiment_pre_install_script != null ? base64encode(var.experiment_pre_install_script) : ""
encoded_post_install_script = var.experiment_post_install_script != null ? base64encode(var.experiment_post_install_script) : ""
@ -244,7 +244,7 @@ resource "coder_script" "claude_code" {
# Disable host header check since AgentAPI is proxied by Coder (which does its own validation)
export AGENTAPI_ALLOWED_HOSTS="*"
# Set chat base path for non-subdomain routing (only set if not using subdomain)
export AGENTAPI_CHAT_BASE_PATH="${local.agentapi_chat_base_path}"
@ -295,4 +295,4 @@ resource "coder_ai_task" "claude_code" {
sidebar_app {
id = coder_app.claude_code_web.id
}
}
}

View File

@ -22,7 +22,8 @@ if (
fs.writeFileSync(
"/home/coder/agentapi-mock.log",
`AGENTAPI_ALLOWED_HOSTS: ${process.env.AGENTAPI_ALLOWED_HOSTS}`,
`AGENTAPI_ALLOWED_HOSTS=${process.env.AGENTAPI_ALLOWED_HOSTS}
AGENTAPI_CHAT_BASE_PATH=${process.env.AGENTAPI_CHAT_BASE_PATH}`,
);
console.log(`starting server on port ${port}`);