Compare commits

...

4 Commits

Author SHA1 Message Date
Ben Potter
1601ab3e8b
feat(.icons): add Lucide SVG icons for skill cards (#880) 2026-05-20 13:18:52 +00:00
dependabot[bot]
f9802456ce
chore(deps): bump the github-actions group across 1 directory with 3 updates (#892)
Bumps the github-actions group with 3 updates in the / directory:
[coder/coder](https://github.com/coder/coder),
[crate-ci/typos](https://github.com/crate-ci/typos) and
[zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action).

Updates `coder/coder` from 2.32.0 to 2.33.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/coder/coder/releases">coder/coder's
releases</a>.</em></p>
<blockquote>
<h2>v2.33.3</h2>
<h2>Changelog</h2>
<blockquote>
<p>[!NOTE]
This is a mainline Coder release. We advise enterprise customers without
a staging environment to install our <a
href="https://github.com/coder/coder/releases/latest">latest stable
release</a> while we refine this version. Learn more about our <a
href="https://coder.com/docs/install/releases">Release Schedule</a>.</p>
</blockquote>
<h3>Bug fixes</h3>
<ul>
<li>Upgrade Go toolchain from 1.25.9 to 1.25.10 (<a
href="https://redirect.github.com/coder/coder/issues/25230">#25230</a>,
e5a96f3608)</li>
<li>Cherry-pick go-git v5.19.0 (CVE-2026-45022) (<a
href="https://redirect.github.com/coder/coder/issues/25229">#25229</a>,
4e4e23539e)</li>
<li>Dashboard: Show Organizations in admin dropdown for single-org OSS
deployments (<a
href="https://redirect.github.com/coder/coder/issues/25175">#25175</a>,
bbca430b4c)</li>
<li>fix(scripts/ironbank): update base image to UBI9 and remove urllib3
(CVE-2026-44431) (<a
href="https://redirect.github.com/coder/coder/issues/25247">#25247</a>,
818fc72802)</li>
<li>Server: Harden Azure identity certificate fetch (cherry-pick v2.33)
(<a
href="https://redirect.github.com/coder/coder/issues/25276">#25276</a>,
844c1e0467)</li>
<li>Verify PKCS7 signature on Azure instance identity tokens (2.33
cherry-pick) (<a
href="https://redirect.github.com/coder/coder/issues/25302">#25302</a>,
2b778f292c)</li>
</ul>
<p>Compare: <a
href="https://github.com/coder/coder/compare/v2.33.2...v2.33.3"><code>v2.33.2...v2.33.3</code></a></p>
<h2>Container image</h2>
<ul>
<li><code>docker pull ghcr.io/coder/coder:2.33.3</code></li>
</ul>
<h2>Install/upgrade</h2>
<p>Refer to our docs to <a
href="https://coder.com/docs/install">install</a> or <a
href="https://coder.com/docs/install/upgrade">upgrade</a> Coder, or use
a release asset below.</p>
<h2>v2.33.2</h2>
<h2>Changelog</h2>
<blockquote>
<p>[!NOTE]
This is a mainline Coder release. We advise enterprise customers without
a staging environment to install our <a
href="https://github.com/coder/coder/releases/latest">latest stable
release</a> while we refine this version. Learn more about our <a
href="https://coder.com/docs/install/releases">Release Schedule</a>.</p>
</blockquote>
<h3>Bug fixes</h3>
<ul>
<li>Backport 11 Coder Agents docs PRs to release/2.33 (<a
href="https://redirect.github.com/coder/coder/issues/25047">#25047</a>,
d622e86fa0)</li>
</ul>
<p>Compare: <a
href="https://github.com/coder/coder/compare/v2.33.1...v2.33.2"><code>v2.33.1...v2.33.2</code></a></p>
<h2>Container image</h2>
<ul>
<li><code>docker pull ghcr.io/coder/coder:2.33.2</code></li>
</ul>
<h2>Install/upgrade</h2>
<p>Refer to our docs to <a
href="https://coder.com/docs/install">install</a> or <a
href="https://coder.com/docs/install/upgrade">upgrade</a> Coder, or use
a release asset below.</p>
<h2>v2.33.1</h2>
<h2>Changelog</h2>
<blockquote>
<p>[!NOTE]
This is a mainline Coder release. We advise enterprise customers without
a staging environment to install our <a
href="https://github.com/coder/coder/releases/latest">latest stable
release</a> while we refine this version. Learn more about our <a
href="https://coder.com/docs/install/releases">Release Schedule</a>.</p>
</blockquote>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="2b778f292c"><code>2b778f2</code></a>
fix: verify PKCS7 signature on Azure instance identity tokens (2.33
cherry-pi...</li>
<li><a
href="844c1e0467"><code>844c1e0</code></a>
fix(coderd): harden Azure identity certificate fetch (cherry-pick v2.33)
(<a
href="https://redirect.github.com/coder/coder/issues/25">#25</a>...</li>
<li><a
href="818fc72802"><code>818fc72</code></a>
fix(scripts/ironbank): update base image to UBI9 and remove urllib3
(CVE-2026...</li>
<li><a
href="bbca430b4c"><code>bbca430</code></a>
fix(site): show Organizations in admin dropdown for single-org OSS
deployment...</li>
<li><a
href="4e4e23539e"><code>4e4e235</code></a>
fix: cherry-pick go-git v5.19.0 (CVE-2026-45022) (<a
href="https://redirect.github.com/coder/coder/issues/25229">#25229</a>)</li>
<li><a
href="e5a96f3608"><code>e5a96f3</code></a>
fix: upgrade Go toolchain from 1.25.9 to 1.25.10 (<a
href="https://redirect.github.com/coder/coder/issues/25230">#25230</a>)</li>
<li><a
href="d622e86fa0"><code>d622e86</code></a>
fix: backport 11 Coder Agents docs PRs to release/2.33 (<a
href="https://redirect.github.com/coder/coder/issues/25047">#25047</a>)</li>
<li><a
href="3e34ba7bf0"><code>3e34ba7</code></a>
chore: remove agents experiment flag and mark feature as beta (<a
href="https://redirect.github.com/coder/coder/issues/24432">#24432</a>)
(<a
href="https://redirect.github.com/coder/coder/issues/25003">#25003</a>)</li>
<li><a
href="f009c17217"><code>f009c17</code></a>
fix(coderd): cut DB fan-out on agent instance-identity auth (backport <a
href="https://redirect.github.com/coder/coder/issues/24973">#24973</a>)...</li>
<li><a
href="17635dde5c"><code>17635dd</code></a>
chore: include pgcoordinator schema changes in 2.33 (<a
href="https://redirect.github.com/coder/coder/issues/24931">#24931</a>)</li>
<li>Additional commits viewable in <a
href="34584e909b...2b778f292c">compare
view</a></li>
</ul>
</details>
<br />

Updates `crate-ci/typos` from 1.45.1 to 1.46.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/releases">crate-ci/typos's
releases</a>.</em></p>
<blockquote>
<h2>v1.46.2</h2>
<h2>[1.46.2] - 2026-05-16</h2>
<h3>Fixes</h3>
<ul>
<li>Don't correct to <code>criterias</code></li>
<li>Don't correct to <code>replaceables</code></li>
</ul>
<h2>v1.46.1</h2>
<h2>[1.46.1] - 2026-05-08</h2>
<h3>Fixes</h3>
<ul>
<li>Don't correct to <code>confidentials</code></li>
</ul>
<h2>v1.46.0</h2>
<h2>[1.46.0] - 2026-04-30</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1531">April
2026</a> changes</li>
</ul>
<h2>v1.45.2</h2>
<h2>[1.45.2] - 2026-04-27</h2>
<h3>Fixes</h3>
<ul>
<li>Ignore ssh ed25519 public keys</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/crate-ci/typos/blob/master/CHANGELOG.md">crate-ci/typos's
changelog</a>.</em></p>
<blockquote>
<h1>Change Log</h1>
<p>All notable changes to this project will be documented in this
file.</p>
<p>The format is based on <a href="https://keepachangelog.com/">Keep a
Changelog</a>
and this project adheres to <a href="https://semver.org/">Semantic
Versioning</a>.</p>
<!-- raw HTML omitted -->
<h2>[Unreleased] - ReleaseDate</h2>
<h2>[1.46.2] - 2026-05-16</h2>
<h3>Fixes</h3>
<ul>
<li>Don't correct to <code>criterias</code></li>
<li>Don't correct to <code>replaceables</code></li>
</ul>
<h2>[1.46.1] - 2026-05-08</h2>
<h3>Fixes</h3>
<ul>
<li>Don't correct to <code>confidentials</code></li>
</ul>
<h2>[1.46.0] - 2026-04-30</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1531">April
2026</a> changes</li>
</ul>
<h2>[1.45.2] - 2026-04-27</h2>
<h3>Fixes</h3>
<ul>
<li>Ignore ssh ed25519 public keys</li>
</ul>
<h2>[1.45.1] - 2026-04-13</h2>
<h3>Fixes</h3>
<ul>
<li><em>(action)</em> Use a temp dir for caching</li>
</ul>
<h2>[1.45.0] - 2026-04-01</h2>
<h3>Features</h3>
<ul>
<li>Updated the dictionary with the <a
href="https://redirect.github.com/crate-ci/typos/issues/1509">March
2026</a> changes</li>
</ul>
<h2>[1.44.0] - 2026-02-27</h2>
<h3>Features</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="aca895bf05"><code>aca895b</code></a>
chore: Release</li>
<li><a
href="4dbdd7509d"><code>4dbdd75</code></a>
docs: Update changelog</li>
<li><a
href="3da2876731"><code>3da2876</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1556">#1556</a>
from epage/replaceable</li>
<li><a
href="8918680477"><code>8918680</code></a>
fix(dict): Don't correct to replaceables</li>
<li><a
href="57d5422e87"><code>57d5422</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1555">#1555</a>
from epage/criteria</li>
<li><a
href="f54668abd7"><code>f54668a</code></a>
fix(dict): Don't correct to criterias</li>
<li><a
href="5374cbf686"><code>5374cbf</code></a>
chore: Release</li>
<li><a
href="52448f5ecf"><code>52448f5</code></a>
docs: Update changelog</li>
<li><a
href="030c719ff1"><code>030c719</code></a>
Merge pull request <a
href="https://redirect.github.com/crate-ci/typos/issues/1552">#1552</a>
from epage/fixes</li>
<li><a
href="7a688c7c08"><code>7a688c7</code></a>
fix(dict): Confidentials isn't valid</li>
<li>Additional commits viewable in <a
href="cf5f1c29a8...aca895bf05">compare
view</a></li>
</ul>
</details>
<br />

Updates `zizmorcore/zizmor-action` from 0.5.3 to 0.5.6
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/zizmorcore/zizmor-action/releases">zizmorcore/zizmor-action's
releases</a>.</em></p>
<blockquote>
<h2>v0.5.6</h2>
<ul>
<li>1.25.2 is now available via the action</li>
<li>1.25.2 is now the default version of zizmor used by the action</li>
</ul>
<h2>v0.5.5</h2>
<p>This is a no-op release.</p>
<h2>v0.5.4</h2>
<ul>
<li>1.25.0 is now available via the action</li>
<li>1.25.0 is now the default version of zizmor used by the action</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5f14fd08f7"><code>5f14fd0</code></a>
Sync zizmor versions (<a
href="https://redirect.github.com/zizmorcore/zizmor-action/issues/114">#114</a>)</li>
<li><a
href="a16621b09c"><code>a16621b</code></a>
Bump pins in README (<a
href="https://redirect.github.com/zizmorcore/zizmor-action/issues/112">#112</a>)</li>
<li><a
href="1c03e047a3"><code>1c03e04</code></a>
chore(deps): bump github/codeql-action from 4.35.2 to 4.35.3 in the
github-ac...</li>
<li><a
href="b572f7b1a1"><code>b572f7b</code></a>
Sync zizmor versions (<a
href="https://redirect.github.com/zizmorcore/zizmor-action/issues/111">#111</a>)</li>
<li><a
href="06928c5dcb"><code>06928c5</code></a>
chore(deps): bump github/codeql-action in the github-actions group (<a
href="https://redirect.github.com/zizmorcore/zizmor-action/issues/109">#109</a>)</li>
<li><a
href="5ea8b96e10"><code>5ea8b96</code></a>
docs: Update link to GitHub docs (<a
href="https://redirect.github.com/zizmorcore/zizmor-action/issues/108">#108</a>)</li>
<li><a
href="849ac26095"><code>849ac26</code></a>
chore(deps): bump the github-actions group with 2 updates (<a
href="https://redirect.github.com/zizmorcore/zizmor-action/issues/106">#106</a>)</li>
<li><a
href="814f9778ac"><code>814f977</code></a>
Bump pins in README (<a
href="https://redirect.github.com/zizmorcore/zizmor-action/issues/103">#103</a>)</li>
<li>See full diff in <a
href="b1d7e1fb5d...5f14fd08f7">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-18 15:03:46 +00:00
ikkz
ee219a8b17
fix(git-clone): propagate pre/post-clone script failures (#891)
## Description

Fix git-clone module to fail fast when `pre_clone_script` or
`post_clone_script` returns a non-zero exit code. Previously, both
scripts were executed but their exit codes were never checked — a
failing pre-clone hook (e.g., a prerequisite check that calls `exit 1`)
was silently ignored and cloning continued. This broke the advertised
"validate prerequisites before cloning" behavior and could leave
workspaces starting with unmet preconditions.

## Type of Change

- [ ] New module
- [ ] New template
- [x] Bug fix
- [ ] Feature/enhancement
- [ ] Documentation
- [ ] Other

## Module Information

**Path:** `registry/coder/modules/git-clone`  
**New version:** `v1.3.1`  
**Breaking change:** [ ] Yes [x] No

## Testing & Validation

- [x] Tests pass (`bun test`)
- [x] Code formatted (`bun fmt`)
- [x] Changes tested locally

## Related Issues

- https://github.com/coder/registry/pull/887#issuecomment-4413765491
- https://github.com/coder/registry/issues/60
- https://github.com/coder/registry/issues/86
2026-05-16 17:33:27 -05:00
Morgan Lunt
4ca251f448
feat(claude-code): add managed_settings input for policy delivery via /etc/claude-code (#863)
## 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>
2026-05-15 08:27:42 -05:00
13 changed files with 225 additions and 33 deletions

View File

@ -37,7 +37,7 @@ jobs:
all:
- '**'
- name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@34584e909bbe6f501fb2cbdc994325b4d3f9e2ef # v2.32.0
uses: coder/coder/.github/actions/setup-tf@2b778f292c2ddf8ac261683d0d5d8a18da1512f6 # v2.33.3
- name: Set up Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
@ -87,13 +87,13 @@ jobs:
bun-version: latest
# Need Terraform for its formatter
- name: Install Terraform
uses: coder/coder/.github/actions/setup-tf@34584e909bbe6f501fb2cbdc994325b4d3f9e2ef # v2.32.0
uses: coder/coder/.github/actions/setup-tf@2b778f292c2ddf8ac261683d0d5d8a18da1512f6 # v2.33.3
- name: Install dependencies
run: bun install
- name: Validate formatting
run: bun fmt:ci
- name: Check for typos
uses: crate-ci/typos@cf5f1c29a8ac336af8568821ec41919923b05a83 # v1.45.1
uses: crate-ci/typos@aca895bf05aec0cb7dffa6f94495e923224d9f17 # v1.46.2
with:
config: .github/typos.toml
validate-readme-files:

View File

@ -31,7 +31,7 @@ jobs:
bun-version: latest
- name: Set up Terraform
uses: coder/coder/.github/actions/setup-tf@34584e909bbe6f501fb2cbdc994325b4d3f9e2ef # v2.32.0
uses: coder/coder/.github/actions/setup-tf@2b778f292c2ddf8ac261683d0d5d8a18da1512f6 # v2.33.3
- name: Install dependencies
run: bun install

View File

@ -27,7 +27,7 @@ jobs:
persist-credentials: false
- name: Run zizmor (blocking, HIGH only)
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
with:
advanced-security: false
annotations: true
@ -49,7 +49,7 @@ jobs:
persist-credentials: false
- name: Run zizmor (SARIF)
uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
with:
inputs: |
.github/workflows

4
.icons/coder-modules.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="7" height="7" x="14" y="3" rx="1"/>
<path d="M10 21V8a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H3"/>
</svg>

After

Width:  |  Height:  |  Size: 339 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="18" height="7" x="3" y="3" rx="1"/>
<rect width="9" height="7" x="3" y="14" rx="1"/>
<rect width="5" height="7" x="16" y="14" rx="1"/>
</svg>

After

Width:  |  Height:  |  Size: 336 B

View File

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

View File

@ -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: {

View File

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

View File

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

View File

@ -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

View File

@ -14,7 +14,7 @@ This module allows you to automatically clone a repository by URL and skip if it
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
@ -28,7 +28,7 @@ module "git-clone" {
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
base_dir = "~/projects/coder"
@ -43,7 +43,7 @@ To use with [Git Authentication](https://coder.com/docs/v2/latest/admin/git-prov
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
}
@ -70,7 +70,7 @@ data "coder_parameter" "git_repo" {
module "git_clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = data.coder_parameter.git_repo.value
}
@ -105,7 +105,7 @@ Configuring `git-clone` for a self-hosted GitHub Enterprise Server running at `g
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://github.example.com/coder/coder/tree/feat/example"
git_providers = {
@ -125,7 +125,7 @@ To GitLab clone with a specific branch like `feat/example`
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://gitlab.com/coder/coder/-/tree/feat/example"
}
@ -137,7 +137,7 @@ Configuring `git-clone` for a self-hosted GitLab running at `gitlab.example.com`
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://gitlab.example.com/coder/coder/-/tree/feat/example"
git_providers = {
@ -159,7 +159,7 @@ For example, to clone the `feat/example` branch:
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
branch_name = "feat/example"
@ -177,7 +177,7 @@ For example, this will clone into the `~/projects/coder/coder-dev` folder:
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
folder_name = "coder-dev"
@ -196,7 +196,7 @@ If not defined, the default, `0`, performs a full clone.
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
depth = 1
@ -212,7 +212,7 @@ This is useful for preparing the environment or validating prerequisites before
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
pre_clone_script = <<-EOT
@ -235,7 +235,7 @@ This is useful for running initialization tasks like installing dependencies or
module "git-clone" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/git-clone/coder"
version = "1.3.0"
version = "1.3.1"
agent_id = coder_agent.example.id
url = "https://github.com/coder/coder"
post_clone_script = <<-EOT

View File

@ -250,13 +250,14 @@ describe("git-clone", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "fake-url",
base_dir: "/tmp",
post_clone_script: "echo 'Post-clone script executed'",
});
const output = await executeScriptInContainer(
state,
"alpine/git",
"sh",
"mkdir -p ~/fake-url && echo 'existing' > ~/fake-url/file.txt",
"mkdir -p /tmp/fake-url && echo 'existing' > /tmp/fake-url/file.txt",
);
expect(output.stdout).toContain("Running post-clone script...");
expect(output.stdout).toContain("Post-clone script executed");
@ -273,4 +274,35 @@ describe("git-clone", async () => {
expect(output.stdout).toContain("Pre-clone script executed");
expect(output.stdout).toContain("Cloning fake-url to ~/fake-url...");
});
it("fails when pre-clone script fails", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "fake-url",
pre_clone_script: "echo 'Pre-clone script failed'; exit 42",
});
const output = await executeScriptInContainer(state, "alpine/git");
expect(output.exitCode).toBe(42);
expect(output.stdout).toContain("Running pre-clone script...");
expect(output.stdout).toContain("Pre-clone script failed");
expect(output.stdout).not.toContain("Cloning fake-url to ~/fake-url...");
});
it("fails when post-clone script fails", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
url: "fake-url",
base_dir: "/tmp",
post_clone_script: "echo 'Post-clone script failed'; exit 43",
});
const output = await executeScriptInContainer(
state,
"alpine/git",
"sh",
"mkdir -p /tmp/fake-url && echo 'existing' > /tmp/fake-url/file.txt",
);
expect(output.exitCode).toBe(43);
expect(output.stdout).toContain("Running post-clone script...");
expect(output.stdout).toContain("Post-clone script failed");
});
});

View File

@ -1,5 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_URL="${REPO_URL}"
CLONE_PATH="${CLONE_PATH}"
BRANCH_NAME="${BRANCH_NAME}"