Compare commits

..

2 Commits

Author SHA1 Message Date
35C4n0r 516b9ce4ae fix(coder/modules/claude-code): update resource count logic for claude_api_key (#814)
## Description
- update resource count logic for claude_api_key

<!-- Briefly describe what this PR does and why -->

## Type of Change

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

## Module Information

<!-- Delete this section if not applicable -->

**Path:** `registry/coder/modules/claude-code`  
**New version:** `v4.8.2`  
**Breaking change:** [ ] Yes [ ] No

## Testing & Validation

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

## Related Issues
Closes: #812
2026-03-26 16:48:43 +05:30
Koury Lape da8e296b1c Fix/dotfiles fish compatibility (#682)
## Description

The dotfiles module does not work when using non-POSIX shells i.e. Fish.

## Type of Change

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

## Module Information

**Path:** `registry/coder/modules/dotfiles`  
**New version:** `v1.4.1`  
**Breaking change:** [ ] Yes [ ] No

## Testing & Validation

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

```
bun test v1.3.8 (b64edcb4)

registry/coder/modules/dotfiles/main.test.ts:
✓ dotfiles > required variables [190.40ms]
✓ dotfiles > missing variable: agent_id [43.12ms]
✓ dotfiles > default output [150.15ms]
✓ dotfiles > set a default dotfiles_uri [159.14ms]
✓ dotfiles > command uses bash for fish shell compatibility [164.08ms]
✓ dotfiles > set custom order for coder_parameter [166.50ms]

 6 pass
 0 fail
 7 expect() calls
Ran 6 tests across 1 file. [1184.00ms]
```

I tested this with a new workspace on Coder v2.27.3 with fish, zsh, and
bash.

---------

Co-authored-by: DevCats <christofer@coder.com>
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
2026-03-20 10:42:34 -05:00
9 changed files with 63 additions and 321 deletions
+9 -9
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 = "4.8.1"
version = "4.8.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_api_key = "xxxx-xxxxx-xxxx"
@@ -60,7 +60,7 @@ By default, when `enable_boundary = true`, the module uses `coder boundary` subc
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.1"
version = "4.8.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_boundary = true
@@ -81,7 +81,7 @@ For tasks integration with AI Bridge, add `enable_aibridge = true` to the [Usage
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.1"
version = "4.8.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
enable_aibridge = true
@@ -110,7 +110,7 @@ data "coder_task" "me" {}
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.1"
version = "4.8.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
ai_prompt = data.coder_task.me.prompt
@@ -133,7 +133,7 @@ This example shows additional configuration options for version pinning, custom
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.1"
version = "4.8.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
@@ -189,7 +189,7 @@ Run and configure Claude Code as a standalone CLI in your workspace.
```tf
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.1"
version = "4.8.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
install_claude_code = true
@@ -211,7 +211,7 @@ variable "claude_code_oauth_token" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.1"
version = "4.8.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
claude_code_oauth_token = var.claude_code_oauth_token
@@ -284,7 +284,7 @@ resource "coder_env" "bedrock_api_key" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.1"
version = "4.8.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
@@ -341,7 +341,7 @@ resource "coder_env" "google_application_credentials" {
module "claude-code" {
source = "registry.coder.com/coder/claude-code/coder"
version = "4.8.1"
version = "4.8.2"
agent_id = coder_agent.main.id
workdir = "/home/coder/project"
model = "claude-sonnet-4@20250514"
+1 -1
View File
@@ -287,7 +287,7 @@ resource "coder_env" "claude_code_oauth_token" {
}
resource "coder_env" "claude_api_key" {
count = local.claude_api_key != "" ? 1 : 0
count = (var.enable_aibridge || (var.claude_api_key != "")) ? 1 : 0
agent_id = var.agent_id
name = "CLAUDE_API_KEY"
@@ -416,7 +416,6 @@ run "test_disable_state_persistence" {
}
}
run "test_no_api_key_no_env" {
command = plan
@@ -431,3 +430,18 @@ run "test_no_api_key_no_env" {
error_message = "CLAUDE_API_KEY should not be created when no API key is provided and aibridge is disabled"
}
}
run "test_api_key_count_with_aibridge_no_override" {
command = plan
variables {
agent_id = "test-agent-count"
workdir = "/home/coder/test"
enable_aibridge = true
}
assert {
condition = length(coder_env.claude_api_key) == 1
error_message = "CLAUDE_API_KEY env should be created when aibridge is enabled, regardless of session_token value"
}
}
+6 -6
View File
@@ -18,7 +18,7 @@ Under the hood, this module uses the [coder dotfiles](https://coder.com/docs/v2/
module "dotfiles" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/dotfiles/coder"
version = "1.4.0"
version = "1.4.1"
agent_id = coder_agent.example.id
}
```
@@ -31,7 +31,7 @@ module "dotfiles" {
module "dotfiles" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/dotfiles/coder"
version = "1.4.0"
version = "1.4.1"
agent_id = coder_agent.example.id
}
```
@@ -42,7 +42,7 @@ module "dotfiles" {
module "dotfiles" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/dotfiles/coder"
version = "1.4.0"
version = "1.4.1"
agent_id = coder_agent.example.id
user = "root"
}
@@ -54,14 +54,14 @@ module "dotfiles" {
module "dotfiles" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/dotfiles/coder"
version = "1.4.0"
version = "1.4.1"
agent_id = coder_agent.example.id
}
module "dotfiles-root" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/dotfiles/coder"
version = "1.4.0"
version = "1.4.1"
agent_id = coder_agent.example.id
user = "root"
dotfiles_uri = module.dotfiles.dotfiles_uri
@@ -90,7 +90,7 @@ You can set a default dotfiles repository for all users by setting the `default_
module "dotfiles" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/dotfiles/coder"
version = "1.4.0"
version = "1.4.1"
agent_id = coder_agent.example.id
default_dotfiles_uri = "https://github.com/coder/dotfiles"
}
@@ -56,6 +56,21 @@ describe("dotfiles", async () => {
}
});
it("command uses bash for fish shell compatibility", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
manual_update: "true",
dotfiles_uri: "https://github.com/test/dotfiles",
});
const app = state.resources.find(
(r) => r.type === "coder_app" && r.name === "dotfiles",
);
expect(app).toBeDefined();
expect(app?.instances[0]?.attributes?.command).toContain("/bin/bash -c");
});
it("set custom order for coder_parameter", async () => {
const order = 99;
const state = await runTerraformApply(import.meta.dir, {
+2 -2
View File
@@ -164,12 +164,12 @@ resource "coder_app" "dotfiles" {
icon = "/icon/dotfiles.svg"
order = var.order
group = var.group
command = templatefile("${path.module}/run.sh", {
command = "/bin/bash -c \"$(echo ${base64encode(templatefile("${path.module}/run.sh", {
DOTFILES_URI : local.dotfiles_uri,
DOTFILES_USER : local.user,
DOTFILES_BRANCH : local.dotfiles_branch,
POST_CLONE_SCRIPT : local.encoded_post_clone_script
})
}))} | base64 -d)\""
}
output "dotfiles_uri" {
+7 -47
View File
@@ -15,7 +15,7 @@ Automatically installs [Node.js](https://github.com/nodejs/node) via [`nvm`](htt
module "nodejs" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/thezoker/nodejs/coder"
version = "1.1.0"
version = "1.0.13"
agent_id = coder_agent.example.id
}
```
@@ -28,55 +28,17 @@ This installs multiple versions of Node.js:
module "nodejs" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/thezoker/nodejs/coder"
version = "1.1.0"
version = "1.0.13"
agent_id = coder_agent.example.id
node_versions = [
"18",
"20",
"node"
]
default_node_version = "20"
default_node_version = "1.0.13"
}
```
## Pre and Post Install Scripts
Use `pre_install_script` and `post_install_script` to run custom scripts before and after Node.js installation.
```tf
module "nodejs" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/thezoker/nodejs/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
pre_install_script = "echo 'Setting up prerequisites...'"
post_install_script = "npm install -g yarn pnpm"
}
```
## Cross-Module Dependency Ordering
This module uses `coder exp sync` to coordinate execution ordering with other modules. It exposes the following outputs for use with `coder exp sync want`:
- `install_script_name` — the sync name for the main Node.js installation script
- `pre_install_script_name` — the sync name for the pre-install script
- `post_install_script_name` — the sync name for the post-install script
For example, to ensure another module waits for Node.js to be fully installed:
```tf
module "nodejs" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/thezoker/nodejs/coder"
version = "1.1.0"
agent_id = coder_agent.example.id
}
# In another module's coder_script, wait for Node.js installation:
# coder exp sync want my-script ${module.nodejs[0].install_script_name}
```
## Full example
A example with all available options:
@@ -85,17 +47,15 @@ A example with all available options:
module "nodejs" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/thezoker/nodejs/coder"
version = "1.1.0"
version = "1.0.13"
agent_id = coder_agent.example.id
nvm_version = "v0.39.7"
nvm_version = "1.0.13"
nvm_install_prefix = "/opt/nvm"
node_versions = [
"16",
"18",
"20",
"node"
]
default_node_version = "20"
pre_install_script = "echo 'Pre-install setup'"
post_install_script = "npm install -g typescript"
default_node_version = "1.0.13"
}
```
+8 -118
View File
@@ -38,125 +38,15 @@ variable "default_node_version" {
default = "node"
}
variable "pre_install_script" {
type = string
description = "Custom script to run before installing Node.js."
default = null
}
variable "post_install_script" {
type = string
description = "Custom script to run after installing Node.js."
default = null
}
locals {
encoded_pre_install_script = var.pre_install_script != null ? base64encode(var.pre_install_script) : ""
encoded_post_install_script = var.post_install_script != null ? base64encode(var.post_install_script) : ""
install_script = templatefile("${path.module}/run.sh", {
NVM_VERSION = var.nvm_version,
INSTALL_PREFIX = var.nvm_install_prefix,
NODE_VERSIONS = join(",", var.node_versions),
DEFAULT = var.default_node_version,
})
encoded_install_script = base64encode(local.install_script)
pre_install_script_name = "nodejs-pre_install_script"
install_script_name = "nodejs-install_script"
post_install_script_name = "nodejs-post_install_script"
module_dir_path = "$HOME/.nodejs-module"
pre_install_path = "${local.module_dir_path}/pre_install.sh"
pre_install_log_path = "${local.module_dir_path}/pre_install.log"
install_path = "${local.module_dir_path}/install.sh"
install_log_path = "${local.module_dir_path}/install.log"
post_install_path = "${local.module_dir_path}/post_install.sh"
post_install_log_path = "${local.module_dir_path}/post_install.log"
}
resource "coder_script" "pre_install_script" {
count = var.pre_install_script == null ? 0 : 1
agent_id = var.agent_id
display_name = "Node.js: Pre-Install"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail
mkdir -p ${local.module_dir_path}
trap 'coder exp sync complete ${local.pre_install_script_name}' EXIT
coder exp sync start ${local.pre_install_script_name}
echo -n '${local.encoded_pre_install_script}' | base64 -d > ${local.pre_install_path}
chmod +x ${local.pre_install_path}
${local.pre_install_path} 2>&1 | tee ${local.pre_install_log_path}
EOT
}
resource "coder_script" "nodejs" {
agent_id = var.agent_id
display_name = "Node.js: Install"
script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail
mkdir -p ${local.module_dir_path}
trap 'coder exp sync complete ${local.install_script_name}' EXIT
%{if var.pre_install_script != null~}
coder exp sync want ${local.install_script_name} ${local.pre_install_script_name}
%{endif~}
coder exp sync start ${local.install_script_name}
echo -n '${local.encoded_install_script}' | base64 -d > ${local.install_path}
chmod +x ${local.install_path}
${local.install_path} 2>&1 | tee ${local.install_log_path}
EOT
agent_id = var.agent_id
display_name = "Node.js:"
script = templatefile("${path.module}/run.sh", {
NVM_VERSION : var.nvm_version,
INSTALL_PREFIX : var.nvm_install_prefix,
NODE_VERSIONS : join(",", var.node_versions),
DEFAULT : var.default_node_version,
})
run_on_start = true
start_blocks_login = true
}
resource "coder_script" "post_install_script" {
count = var.post_install_script != null ? 1 : 0
agent_id = var.agent_id
display_name = "Node.js: Post-Install"
run_on_start = true
script = <<-EOT
#!/bin/bash
set -o errexit
set -o pipefail
mkdir -p ${local.module_dir_path}
trap 'coder exp sync complete ${local.post_install_script_name}' EXIT
coder exp sync want ${local.post_install_script_name} ${local.install_script_name}
coder exp sync start ${local.post_install_script_name}
echo -n '${local.encoded_post_install_script}' | base64 -d > ${local.post_install_path}
chmod +x ${local.post_install_path}
${local.post_install_path} 2>&1 | tee ${local.post_install_log_path}
EOT
}
output "pre_install_script_name" {
description = "The name of the pre-install script for coder exp sync coordination."
value = local.pre_install_script_name
}
output "install_script_name" {
description = "The name of the install script for coder exp sync coordination."
value = local.install_script_name
}
output "post_install_script_name" {
description = "The name of the post-install script for coder exp sync coordination."
value = local.post_install_script_name
}
@@ -1,137 +0,0 @@
run "test_nodejs_basic" {
command = plan
variables {
agent_id = "test-agent-123"
}
assert {
condition = var.agent_id == "test-agent-123"
error_message = "Agent ID variable should be set correctly"
}
assert {
condition = var.nvm_version == "master"
error_message = "nvm_version should default to master"
}
assert {
condition = var.default_node_version == "node"
error_message = "default_node_version should default to node"
}
assert {
condition = var.pre_install_script == null
error_message = "pre_install_script should default to null"
}
assert {
condition = var.post_install_script == null
error_message = "post_install_script should default to null"
}
assert {
condition = output.install_script_name == "nodejs-install_script"
error_message = "install_script_name output should be set"
}
}
run "test_with_scripts" {
command = plan
variables {
agent_id = "test-agent-scripts"
pre_install_script = "echo 'Pre-install script'"
post_install_script = "echo 'Post-install script'"
}
assert {
condition = var.pre_install_script == "echo 'Pre-install script'"
error_message = "Pre-install script should be set correctly"
}
assert {
condition = var.post_install_script == "echo 'Post-install script'"
error_message = "Post-install script should be set correctly"
}
assert {
condition = output.pre_install_script_name == "nodejs-pre_install_script"
error_message = "pre_install_script_name output should be set"
}
assert {
condition = output.post_install_script_name == "nodejs-post_install_script"
error_message = "post_install_script_name output should be set"
}
}
run "test_custom_options" {
command = plan
variables {
agent_id = "test-agent-custom"
nvm_version = "v0.39.7"
nvm_install_prefix = ".custom-nvm"
node_versions = ["18", "20", "node"]
default_node_version = "20"
}
assert {
condition = var.nvm_version == "v0.39.7"
error_message = "nvm_version should be set to v0.39.7"
}
assert {
condition = var.nvm_install_prefix == ".custom-nvm"
error_message = "nvm_install_prefix should be set correctly"
}
assert {
condition = length(var.node_versions) == 3
error_message = "node_versions should have 3 entries"
}
assert {
condition = var.default_node_version == "20"
error_message = "default_node_version should be set to 20"
}
}
run "test_with_pre_install_only" {
command = plan
variables {
agent_id = "test-agent-pre"
pre_install_script = "echo 'pre-install'"
}
assert {
condition = var.pre_install_script != null
error_message = "Pre-install script should be set"
}
assert {
condition = var.post_install_script == null
error_message = "Post-install script should default to null"
}
}
run "test_with_post_install_only" {
command = plan
variables {
agent_id = "test-agent-post"
post_install_script = "echo 'post-install'"
}
assert {
condition = var.pre_install_script == null
error_message = "Pre-install script should default to null"
}
assert {
condition = var.post_install_script != null
error_message = "Post-install script should be set"
}
}