mirror of
https://github.com/coder/registry.git
synced 2026-06-05 22:18:15 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4ca251f448 |
@@ -13,7 +13,7 @@ Install and configure the [Claude Code](https://docs.anthropic.com/en/docs/agent
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
anthropic_api_key = "xxxx-xxxxx-xxxx"
|
||||
}
|
||||
@@ -47,7 +47,7 @@ locals {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = local.claude_workdir
|
||||
anthropic_api_key = "xxxx-xxxxx-xxxx"
|
||||
@@ -78,7 +78,7 @@ resource "coder_app" "claude" {
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
enable_ai_gateway = true
|
||||
@@ -95,6 +95,33 @@ Claude Code then routes API requests through Coder's AI Gateway instead of direc
|
||||
> [!CAUTION]
|
||||
> `enable_ai_gateway = true` is mutually exclusive with `anthropic_api_key` and `claude_code_oauth_token`. Setting any of them together fails at plan time.
|
||||
|
||||
### Enterprise policy via managed settings
|
||||
|
||||
The `managed_settings` input writes a policy file to `/etc/claude-code/managed-settings.d/10-coder.json` inside the workspace. Claude Code reads this directory at startup with the highest configuration precedence, so users cannot override these values in their own `~/.claude/settings.json`. This is a local file mechanism and works with any inference backend (Anthropic API, AWS Bedrock, Google Vertex AI, or AI Gateway).
|
||||
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "5.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
anthropic_api_key = "xxxx-xxxxx-xxxx"
|
||||
|
||||
managed_settings = {
|
||||
permissions = {
|
||||
defaultMode = "acceptEdits"
|
||||
disableBypassPermissionsMode = "disable"
|
||||
deny = ["Bash(curl:*)", "Bash(wget:*)", "WebFetch"]
|
||||
}
|
||||
env = {
|
||||
DISABLE_TELEMETRY = "0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See the [Claude Code settings reference](https://docs.anthropic.com/en/docs/claude-code/settings) for the full schema. Common keys: `permissions` (`defaultMode`, `allow`, `deny`, `disableBypassPermissionsMode`, `additionalDirectories`), `env`, `model`, `apiKeyHelper`, `hooks`, `cleanupPeriodDays`.
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This example shows version pinning, a pre-installed binary path, a custom model, and MCP servers.
|
||||
@@ -102,7 +129,7 @@ This example shows version pinning, a pre-installed binary path, a custom model,
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
|
||||
@@ -166,7 +193,7 @@ Downstream `coder_script` resources can wait for this module's install pipeline
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
anthropic_api_key = "xxxx-xxxxx-xxxx"
|
||||
@@ -252,7 +279,7 @@ resource "coder_env" "bedrock_api_key" {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
||||
@@ -309,7 +336,7 @@ resource "coder_env" "google_application_credentials" {
|
||||
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
model = "claude-sonnet-4@20250514"
|
||||
@@ -350,7 +377,7 @@ The module automatically tags every span and metric with `coder.workspace_id`, `
|
||||
```tf
|
||||
module "claude-code" {
|
||||
source = "registry.coder.com/coder/claude-code/coder"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
agent_id = coder_agent.main.id
|
||||
workdir = "/home/coder/project"
|
||||
anthropic_api_key = "xxxx-xxxxx-xxxx"
|
||||
|
||||
@@ -382,10 +382,13 @@ describe("claude-code", async () => {
|
||||
const parsed = JSON.parse(claudeConfig);
|
||||
expect(parsed.autoUpdaterStatus).toBe("disabled");
|
||||
expect(parsed.hasCompletedOnboarding).toBe(true);
|
||||
expect(parsed.bypassPermissionsModeAccepted).toBe(true);
|
||||
expect(parsed.hasAcknowledgedCostThreshold).toBe(true);
|
||||
expect(parsed.projects[workdir].hasCompletedProjectOnboarding).toBe(true);
|
||||
expect(parsed.projects[workdir].hasTrustDialogAccepted).toBe(true);
|
||||
// Permission posture is delivered via /etc/claude-code/managed-settings.d/,
|
||||
// not user-writable ~/.claude.json acceptance flags.
|
||||
expect(parsed.bypassPermissionsModeAccepted).toBeUndefined();
|
||||
expect(parsed.autoModeAccepted).toBeUndefined();
|
||||
});
|
||||
|
||||
test("standalone-mode-with-oauth-token", async () => {
|
||||
@@ -413,7 +416,7 @@ describe("claude-code", async () => {
|
||||
);
|
||||
const parsed = JSON.parse(claudeConfig);
|
||||
expect(parsed.hasCompletedOnboarding).toBe(true);
|
||||
expect(parsed.bypassPermissionsModeAccepted).toBe(true);
|
||||
expect(parsed.bypassPermissionsModeAccepted).toBeUndefined();
|
||||
});
|
||||
|
||||
test("standalone-mode-no-auth", async () => {
|
||||
@@ -436,6 +439,49 @@ describe("claude-code", async () => {
|
||||
expect(resp.stdout.trim()).toBe("ABSENT");
|
||||
});
|
||||
|
||||
test("claude-managed-settings-written", async () => {
|
||||
const { id, scripts } = await setup({
|
||||
moduleVariables: {
|
||||
managed_settings: JSON.stringify({
|
||||
permissions: {
|
||||
defaultMode: "acceptEdits",
|
||||
disableBypassPermissionsMode: "disable",
|
||||
deny: ["Bash(rm -rf*)"],
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
await runScripts(id, scripts);
|
||||
|
||||
const policy = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
"cat /etc/claude-code/managed-settings.d/10-coder.json",
|
||||
]);
|
||||
expect(policy.exitCode).toBe(0);
|
||||
expect(policy.stdout).toContain('"defaultMode":"acceptEdits"');
|
||||
expect(policy.stdout).toContain('"disableBypassPermissionsMode":"disable"');
|
||||
expect(policy.stdout).toContain('"deny":["Bash(rm -rf*)"]');
|
||||
|
||||
const installLog = await readFileContainer(
|
||||
id,
|
||||
"/home/coder/.coder-modules/coder/claude-code/logs/install.log",
|
||||
);
|
||||
expect(installLog).toContain("Wrote Claude Code managed settings");
|
||||
});
|
||||
|
||||
test("claude-managed-settings-not-set", async () => {
|
||||
const { id, scripts } = await setup();
|
||||
await runScripts(id, scripts);
|
||||
|
||||
const resp = await execContainer(id, [
|
||||
"bash",
|
||||
"-c",
|
||||
"test -e /etc/claude-code/managed-settings.d/10-coder.json && echo EXISTS || echo ABSENT",
|
||||
]);
|
||||
expect(resp.stdout.trim()).toBe("ABSENT");
|
||||
});
|
||||
|
||||
test("telemetry-otel", async () => {
|
||||
const { coderEnvVars } = await setup({
|
||||
moduleVariables: {
|
||||
|
||||
@@ -102,6 +102,12 @@ variable "claude_binary_path" {
|
||||
}
|
||||
}
|
||||
|
||||
variable "managed_settings" {
|
||||
type = any
|
||||
description = "Policy settings written to /etc/claude-code/managed-settings.d/10-coder.json. Highest-precedence client config; works with any inference backend (Anthropic API, Bedrock, Vertex, AI Gateway). See https://docs.anthropic.com/en/docs/claude-code/settings for the schema."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "enable_ai_gateway" {
|
||||
type = bool
|
||||
description = "Use AI Gateway for Claude Code. https://coder.com/docs/ai-coder/ai-gateway"
|
||||
@@ -237,6 +243,7 @@ locals {
|
||||
ARG_MCP = var.mcp != "" ? base64encode(var.mcp) : ""
|
||||
ARG_MCP_CONFIG_REMOTE_PATH = base64encode(jsonencode(var.mcp_config_remote_path))
|
||||
ARG_ENABLE_AI_GATEWAY = tostring(var.enable_ai_gateway)
|
||||
ARG_MANAGED_SETTINGS_JSON = var.managed_settings != null ? base64encode(jsonencode(var.managed_settings)) : ""
|
||||
})
|
||||
module_dir_name = ".coder-modules/coder/claude-code"
|
||||
}
|
||||
|
||||
@@ -283,3 +283,47 @@ run "test_workdir_optional" {
|
||||
error_message = "workdir should default to null when omitted"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_managed_settings" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-managed-settings"
|
||||
workdir = "/home/coder/project"
|
||||
managed_settings = {
|
||||
permissions = {
|
||||
defaultMode = "acceptEdits"
|
||||
disableBypassPermissionsMode = "disable"
|
||||
deny = ["Bash(rm -rf*)"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.managed_settings.permissions.defaultMode == "acceptEdits"
|
||||
error_message = "managed_settings should accept the permissions object"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(local.install_script, "/etc/claude-code/managed-settings.d")
|
||||
error_message = "install script should reference the managed-settings.d drop-in directory"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(local.install_script, base64encode(jsonencode(var.managed_settings)))
|
||||
error_message = "install script should embed the base64-encoded managed_settings JSON"
|
||||
}
|
||||
}
|
||||
|
||||
run "test_managed_settings_default_null" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "test-agent-managed-settings-default"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = var.managed_settings == null
|
||||
error_message = "managed_settings should default to null when omitted"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ ARG_CLAUDE_BINARY_PATH="$${ARG_CLAUDE_BINARY_PATH//\$HOME/$HOME}"
|
||||
ARG_MCP=$(echo -n '${ARG_MCP}' | base64 -d)
|
||||
ARG_MCP_CONFIG_REMOTE_PATH=$(echo -n '${ARG_MCP_CONFIG_REMOTE_PATH}' | base64 -d)
|
||||
ARG_ENABLE_AI_GATEWAY='${ARG_ENABLE_AI_GATEWAY}'
|
||||
ARG_MANAGED_SETTINGS_JSON=$(echo -n '${ARG_MANAGED_SETTINGS_JSON}' | base64 -d)
|
||||
|
||||
export PATH="$${ARG_CLAUDE_BINARY_PATH}:$PATH"
|
||||
|
||||
@@ -29,6 +30,7 @@ printf "ARG_CLAUDE_BINARY_PATH: %s\n" "$${ARG_CLAUDE_BINARY_PATH}"
|
||||
printf "ARG_MCP: %s\n" "$${ARG_MCP}"
|
||||
printf "ARG_MCP_CONFIG_REMOTE_PATH: %s\n" "$${ARG_MCP_CONFIG_REMOTE_PATH}"
|
||||
printf "ARG_ENABLE_AI_GATEWAY: %s\n" "$${ARG_ENABLE_AI_GATEWAY}"
|
||||
printf "ARG_MANAGED_SETTINGS_JSON: %s\n" "$${ARG_MANAGED_SETTINGS_JSON}"
|
||||
|
||||
echo "--------------------------------"
|
||||
|
||||
@@ -144,6 +146,32 @@ function setup_claude_configurations() {
|
||||
|
||||
}
|
||||
|
||||
function write_managed_settings() {
|
||||
if [ -z "$${ARG_MANAGED_SETTINGS_JSON}" ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local dropin_dir="/etc/claude-code/managed-settings.d"
|
||||
local target="$${dropin_dir}/10-coder.json"
|
||||
|
||||
if ! echo "$${ARG_MANAGED_SETTINGS_JSON}" | jq empty 2> /dev/null; then
|
||||
echo "Warning: managed_settings is not valid JSON, skipping policy write"
|
||||
return
|
||||
fi
|
||||
|
||||
if command_exists sudo; then
|
||||
sudo mkdir -p "$${dropin_dir}"
|
||||
echo "$${ARG_MANAGED_SETTINGS_JSON}" | sudo tee "$${target}" > /dev/null
|
||||
sudo chmod 0644 "$${target}"
|
||||
else
|
||||
mkdir -p "$${dropin_dir}"
|
||||
echo "$${ARG_MANAGED_SETTINGS_JSON}" > "$${target}"
|
||||
chmod 0644 "$${target}"
|
||||
fi
|
||||
|
||||
echo "Wrote Claude Code managed settings to $${target}"
|
||||
}
|
||||
|
||||
function configure_standalone_mode() {
|
||||
echo "Configuring Claude Code for standalone mode..."
|
||||
|
||||
@@ -158,8 +186,6 @@ function configure_standalone_mode() {
|
||||
echo "Updating existing Claude configuration at $${claude_config}"
|
||||
|
||||
jq '.autoUpdaterStatus = "disabled" |
|
||||
.autoModeAccepted = true |
|
||||
.bypassPermissionsModeAccepted = true |
|
||||
.hasAcknowledgedCostThreshold = true |
|
||||
.hasCompletedOnboarding = true' \
|
||||
"$${claude_config}" > "$${claude_config}.tmp" && mv "$${claude_config}.tmp" "$${claude_config}"
|
||||
@@ -168,8 +194,6 @@ function configure_standalone_mode() {
|
||||
cat > "$${claude_config}" << EOF
|
||||
{
|
||||
"autoUpdaterStatus": "disabled",
|
||||
"autoModeAccepted": true,
|
||||
"bypassPermissionsModeAccepted": true,
|
||||
"hasAcknowledgedCostThreshold": true,
|
||||
"hasCompletedOnboarding": true
|
||||
}
|
||||
@@ -189,4 +213,5 @@ EOF
|
||||
|
||||
install_claude_code_cli
|
||||
setup_claude_configurations
|
||||
write_managed_settings
|
||||
configure_standalone_mode
|
||||
|
||||
Reference in New Issue
Block a user