## Problem
The module configures Claude Code's permission posture by reaching
around the permission system rather than through it:
- `scripts/install.sh` writes `bypassPermissionsModeAccepted`,
`autoModeAccepted`, and `primaryApiKey` directly into the user-writable
`~/.claude.json`. Any process in the workspace can read the API key or
flip the acceptance flags back.
- `scripts/start.sh` adds `--dangerously-skip-permissions` to every task
launch, even when the template author set an explicit `permission_mode`.
The README has to carry a security warning telling people the module
bypasses permission checks.
- `permission_mode`, `allowed_tools`, and `disallowed_tools` each plumb
through a different ad-hoc path (CLI flag, `coder` subcommand) instead
of a single policy surface.
## Change
Add a `managed_settings` input that renders to
`/etc/claude-code/managed-settings.d/10-coder.json`. Claude Code reads
that drop-in directory at startup with the highest configuration
precedence (above `~/.claude/settings.json` and project settings), so
template authors get an admin-controlled policy file that users inside
the workspace cannot override. The mechanism is a local file read with
no API call, so it works identically for the Anthropic API, AWS Bedrock,
Google Vertex AI, and AI Bridge / AI Gateway.
```hcl
managed_settings = {
permissions = {
defaultMode = "acceptEdits"
disableBypassPermissionsMode = "disable"
deny = ["Bash(curl:*)", "WebFetch"]
}
}
```
Supporting changes:
- `install.sh` writes the policy file (root-owned, 0644) and stops
writing `bypassPermissionsModeAccepted`, `autoModeAccepted`, and
`primaryApiKey` into `~/.claude.json`. The API key is already exported
via `coder_env` as `CLAUDE_API_KEY`; duplicating it on disk is
unnecessary. `hasCompletedOnboarding` stays because there is no env-var
alternative for it.
- `start.sh` only adds `--dangerously-skip-permissions` for tasks when
no explicit `permission_mode` is set (same fix as #846; included here so
this PR is self-contained, happy to drop if #846 lands first).
- `permission_mode`, `allowed_tools`, and `disallowed_tools` are marked
deprecated and shimmed into `managed_settings.permissions` for one
release when `managed_settings` is not provided.
- README security warning rewritten to point at the policy mechanism
instead of telling people the module is unsafe by design.
## Relationship to #861
#861 strips this module to install-and-configure and removes
`permission_mode` / `allowed_tools` / `disallowed_tools` outright.
`managed_settings` is the natural replacement for those: it is
install-time (survives the `start.sh` removal), it covers everything the
dropped variables did plus `hooks`, `env`, `model`, `apiKeyHelper`, and
the rest of the settings schema, and it does not require the module to
know anything about how Claude is launched. If #861 lands first I will
rebase this on top and drop the deprecation shim and the `start.sh`
hunk.
## Validation
- `terraform fmt` / `terraform validate` clean
- New tests: `claude-managed-settings-written`,
`claude-managed-settings-legacy-shim`,
`claude-no-policy-keys-in-claudejson`, plus an assertion in
`claude-auto-permission-mode` that `--dangerously-skip-permissions` is
absent when a mode is set
- Manually verified `/etc/claude-code/managed-settings.d/*.json`
precedence in the Claude Code CLI source
Closes #818. Relates to #284, #846, #861.
Disclosure: I work at Anthropic on the Claude Code team. Happy to adjust
scope or split this further if that is easier to review.
---------
Co-authored-by: DevCats <chris@dualriver.com>
Co-authored-by: DevCats <christofer@coder.com>
15 KiB
| display_name | description | icon | verified | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Claude Code | Install and configure the Claude Code CLI in your workspace. | ../../../../.icons/claude.svg | true |
|
Claude Code
Install and configure the Claude Code CLI in your workspace. Starting Claude is left to the caller (template command, IDE launcher, or a custom coder_script).
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0"
agent_id = coder_agent.main.id
anthropic_api_key = "xxxx-xxxxx-xxxx"
}
Warning
If upgrading from v4.x.x of this module: v5 is a major refactor that drops support for Coder Tasks and Boundary. We plan to add those back in a follow-up. Keep using v4.x.x if you depend on them. See #861 for the full migration guide.
Prerequisites
Provide exactly one authentication method:
- Anthropic API key: get one from the Anthropic Console and pass it as
anthropic_api_key. - Claude.ai OAuth token (Pro, Max, or Enterprise accounts): generate one by running
claude setup-tokenlocally and pass it asclaude_code_oauth_token. - Coder AI Gateway (Coder Premium, Coder >= 2.30.0): set
enable_ai_gateway = true. The module authenticates against the gateway using the workspace owner's session token. Do not combine withanthropic_api_keyorclaude_code_oauth_token.
workdir
workdir is optional. When set, the module pre-creates the directory if it is missing and pre-accepts the Claude Code trust/onboarding prompt for it in ~/.claude.json. Leave workdir unset if you only want the module to install the CLI and configure authentication; users can still open any project interactively and accept the trust dialog per project.
Examples
Standalone mode with a launcher app
Authenticate Claude directly against Anthropic's API and add a coder_app that users can click from the workspace dashboard to open an interactive Claude session.
locals {
claude_workdir = "/home/coder/project"
}
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "5.2.0"
agent_id = coder_agent.main.id
workdir = local.claude_workdir
anthropic_api_key = "xxxx-xxxxx-xxxx"
}
resource "coder_app" "claude" {
agent_id = coder_agent.main.id
slug = "claude"
display_name = "Claude Code"
icon = "/icon/claude.svg"
open_in = "slim-window"
command = <<-EOT
#!/bin/bash
set -e
cd ${local.claude_workdir}
claude
EOT
}
Note
coder_app.commandruns when the user clicks the app tile. Combine withanthropic_api_key,claude_code_oauth_token, orenable_ai_gateway = trueon the module to pre-authenticate the CLI.
Usage with AI Gateway
AI Gateway is a Premium Coder feature that provides centralized LLM proxy management. Requires Coder >= 2.30.0.
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"
enable_ai_gateway = true
}
When enable_ai_gateway = true, the module sets:
ANTHROPIC_BASE_URLto${data.coder_workspace.me.access_url}/api/v2/aibridge/anthropicANTHROPIC_AUTH_TOKENto the workspace owner's Coder session token
Claude Code then routes API requests through Coder's AI Gateway instead of directly to Anthropic.
Caution
enable_ai_gateway = trueis mutually exclusive withanthropic_api_keyandclaude_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).
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 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.
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"
claude_code_version = "2.0.62" # Pin to a specific Claude CLI version.
# Skip the module's installer and point at a pre-installed Claude binary.
# claude_binary_path can only be customized when install_claude_code is false.
install_claude_code = false
claude_binary_path = "/opt/claude/bin"
model = "sonnet"
mcp = <<-EOF
{
"mcpServers": {
"my-custom-tool": {
"command": "my-tool-server",
"args": ["--port", "8080"]
}
}
}
EOF
mcp_config_remote_path = [
"https://gist.githubusercontent.com/35C4n0r/cd8dce70360e5d22a070ae21893caed4/raw/",
"https://raw.githubusercontent.com/coder/coder/main/.mcp.json"
]
}
Note
Swap
anthropic_api_keyforclaude_code_oauth_token = "xxxxx-xxxx-xxxx"to authenticate via a Claude.ai OAuth token instead. Pass exactly one.
Note
Servers configured through
mcpormcp_config_remote_pathare added at Claude Code's user scope, making them available across every project the workspace owner opens. For project-local MCP servers, commit a.mcp.jsonto the project repository instead.
Note
Remote URLs should return a JSON body in the following format:
{ "mcpServers": { "server-name": { "command": "some-command", "args": ["arg1", "arg2"] } } }The
Content-Typeheader doesn't matter, bothtext/plainandapplication/jsonwork fine.
Serialize a downstream coder_script after the install pipeline
The module exposes the coder exp sync name of each script it creates via the scripts output: an ordered list (pre_install, install, post_install) of names for scripts this module actually creates. Scripts that were not configured are absent from the list.
Downstream coder_script resources can wait for this module's install pipeline to finish using coder exp sync want <self> <each name>:
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"
}
resource "coder_script" "post_claude" {
agent_id = coder_agent.main.id
display_name = "Run after Claude Code install"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -euo pipefail
trap 'coder exp sync complete post-claude' EXIT
coder exp sync want post-claude ${join(" ", module.claude-code.scripts)}
coder exp sync start post-claude
# Your work here runs after claude-code finishes installing.
claude --version
EOT
}
Usage with AWS Bedrock
Prerequisites
AWS account with Bedrock access, Claude models enabled in Bedrock console, and appropriate IAM permissions.
Configure Claude Code to use AWS Bedrock for accessing Claude models through your AWS infrastructure.
resource "coder_env" "bedrock_use" {
agent_id = coder_agent.main.id
name = "CLAUDE_CODE_USE_BEDROCK"
value = "1"
}
resource "coder_env" "aws_region" {
agent_id = coder_agent.main.id
name = "AWS_REGION"
value = "us-east-1" # Choose your preferred region
}
# Option 1: Using AWS credentials
variable "aws_access_key_id" {
type = string
description = "Your AWS access key ID. Create this in the AWS IAM console under 'Security credentials'."
sensitive = true
}
variable "aws_secret_access_key" {
type = string
description = "Your AWS secret access key. This is shown once when you create an access key in the AWS IAM console."
sensitive = true
}
resource "coder_env" "aws_access_key_id" {
agent_id = coder_agent.main.id
name = "AWS_ACCESS_KEY_ID"
value = var.aws_access_key_id
}
resource "coder_env" "aws_secret_access_key" {
agent_id = coder_agent.main.id
name = "AWS_SECRET_ACCESS_KEY"
value = var.aws_secret_access_key
}
# Option 2: Using Bedrock API key (simpler)
variable "aws_bearer_token_bedrock" {
type = string
description = "Your AWS Bedrock bearer token. This provides access to Bedrock without needing separate access key and secret key."
sensitive = true
}
resource "coder_env" "bedrock_api_key" {
agent_id = coder_agent.main.id
name = "AWS_BEARER_TOKEN_BEDROCK"
value = var.aws_bearer_token_bedrock
}
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"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
}
Note
For additional Bedrock configuration options (model selection, token limits, region overrides, etc.), see the Claude Code Bedrock documentation.
Usage with Google Vertex AI
Prerequisites
GCP project with Vertex AI API enabled, Claude models enabled through Model Garden, service account with Vertex AI permissions, and appropriate IAM permissions (Vertex AI User role).
Configure Claude Code to use Google Vertex AI for accessing Claude models through Google Cloud Platform.
variable "vertex_sa_json" {
type = string
description = "The complete JSON content of your Google Cloud service account key file. Create a service account in the GCP Console under 'IAM & Admin > Service Accounts', then create and download a JSON key. Copy the entire JSON content into this variable."
sensitive = true
}
resource "coder_env" "vertex_use" {
agent_id = coder_agent.main.id
name = "CLAUDE_CODE_USE_VERTEX"
value = "1"
}
resource "coder_env" "vertex_project_id" {
agent_id = coder_agent.main.id
name = "ANTHROPIC_VERTEX_PROJECT_ID"
value = "your-gcp-project-id"
}
resource "coder_env" "cloud_ml_region" {
agent_id = coder_agent.main.id
name = "CLOUD_ML_REGION"
value = "global"
}
resource "coder_env" "vertex_sa_json" {
agent_id = coder_agent.main.id
name = "VERTEX_SA_JSON"
value = var.vertex_sa_json
}
resource "coder_env" "google_application_credentials" {
agent_id = coder_agent.main.id
name = "GOOGLE_APPLICATION_CREDENTIALS"
value = "/tmp/gcp-sa.json"
}
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"
model = "claude-sonnet-4@20250514"
pre_install_script = <<-EOT
#!/bin/bash
# Write the service account JSON to a file
echo "$VERTEX_SA_JSON" > /tmp/gcp-sa.json
# Install prerequisite packages
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates gnupg curl
# Add Google Cloud public key
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
# Add Google Cloud SDK repo to apt sources
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee /etc/apt/sources.list.d/google-cloud-sdk.list
# Update and install the Google Cloud SDK
sudo apt-get update && sudo apt-get install -y google-cloud-cli
# Authenticate gcloud with the service account
gcloud auth activate-service-account --key-file=/tmp/gcp-sa.json
EOT
}
Note
For additional Vertex AI configuration options (model selection, token limits, region overrides, etc.), see the Claude Code Vertex AI documentation.
Telemetry export (OpenTelemetry)
Claude Code can emit OpenTelemetry metrics and events covering token usage, tool calls, session lifecycle, and errors (see the monitoring docs). Set telemetry.enabled = true and point otlp_endpoint at your OTLP collector.
The module automatically tags every span and metric with coder.workspace_id, coder.workspace_name, coder.workspace_owner, and coder.template_name via OTEL_RESOURCE_ATTRIBUTES, so Claude Code telemetry can be joined directly against Coder's audit logs and exectrace records on workspace_id.
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"
telemetry = {
enabled = true
otlp_endpoint = "http://otel-collector.observability:4317"
otlp_protocol = "grpc"
otlp_headers = {
authorization = "Bearer ${var.otel_token}"
}
resource_attributes = {
"service.name" = "claude-code"
}
}
}
Troubleshooting
If you encounter any issues, check the log files in the ~/.coder-modules/coder/claude-code/logs directory within your workspace for detailed information.
# Installation logs
cat ~/.coder-modules/coder/claude-code/logs/install.log
# Pre/post install script logs
cat ~/.coder-modules/coder/claude-code/logs/pre_install.log
cat ~/.coder-modules/coder/claude-code/logs/post_install.log