Skip to content

Latest commit

 

History

History
145 lines (110 loc) · 8.65 KB

AGENTS.md

File metadata and controls

145 lines (110 loc) · 8.65 KB

AGENTS.md

Coder Registry: Terraform modules/templates for Coder workspaces under registry/[namespace]/modules/ and registry/[namespace]/templates/.

Commands

bun run fmt                                           # Format code (Prettier + Terraform) - run before commits
bun run check:terraform                               # Run all Terraform tests
bun run check:typescript                              # Run all TypeScript tests
bun check                                             # Run all tests + validation (validate, shellcheck, terraform, typescript)
terraform init -upgrade && terraform test -verbose    # Test single module (run from module dir)
bun test main.test.ts                                 # Run single TS test (from module dir)
./scripts/terraform_validate.sh                       # Validate Terraform syntax
./scripts/new_module.sh ns/name                       # Create new module scaffold
.github/scripts/version-bump.sh patch | minor | major # Bump module version after changes

Structure

  • Modules: registry/[ns]/modules/[name]/ with main.tf, README.md (YAML frontmatter), .tftest.hcl (required)
  • Templates: registry/[ns]/templates/[name]/ with main.tf, README.md
  • Validation: cmd/readmevalidation/ (Go) validates structure/frontmatter; URLs must be relative, not absolute

Module Data Layout

All runtime data a module writes on the workspace MUST live under a single per-module root:

$HOME/.coder-modules/<namespace>/<module-name>/

For a Coder-owned module named claude-code, the root is $HOME/.coder-modules/coder/claude-code/.

Within that root, use these standard subdirectories:

Subdirectory Purpose Example
logs/ Output from install, start, or any script $HOME/.coder-modules/coder/claude-code/logs/install.log
scripts/ Scripts materialized at runtime (if any) $HOME/.coder-modules/coder/claude-code/scripts/install.sh
  • Name log files after the script that produced them (install.sh writes to logs/install.log, start.sh writes to logs/start.log).
  • Always mkdir -p the target directory before writing; do not assume it exists.
  • Do not write module runtime data to $HOME directly, to ad-hoc paths like ~/.<module>-module/, or to /tmp/ for anything that must survive the session.
  • Tool-specific data (config files, caches, state, etc.) lives wherever the tool expects; only standardize paths the module itself controls.
  • READMEs and tests should reference paths under this root so troubleshooting has one place to look.
  • New modules MUST follow this layout. Existing modules should migrate to it when they are next touched.

Use coder-utils for Script Orchestration

For any new module that runs scripts (or when reworking an existing one), use the coder-utils module to orchestrate pre_install, install, post_install, and start scripts instead of hand-rolling coder_script resources.

  • coder-utils handles script ordering via coder exp sync, materializes scripts under module_directory/scripts/ (e.g., install.sh, start.sh), and writes logs to module_directory/logs/ automatically, which aligns with the Module Data Layout above.
  • Set module_directory = "$HOME/.coder-modules/<namespace>/<module-name>" so the standard root, scripts/, and logs/ subdirectories fall out for free.

Passing scripts to coder-utils

Store each script as a .tftpl file under scripts/. Render it at plan time in a locals block using templatefile(), then pass the rendered string directly to the coder-utils module.

Encoding rules for template variables:

Value type Terraform side Template (.tftpl) side
String / path pass as-is ARG_FOO='${ARG_FOO}'
Boolean tostring(var.foo) ARG_FOO='${ARG_FOO}'
Free-form string (may contain quotes) base64encode(var.foo) ARG_FOO=$(echo -n '${ARG_FOO}' | base64 -d)
Object / list (JSON) base64encode(jsonencode(var.foo)) ARG_FOO=$(echo -n '${ARG_FOO}' | base64 -d)

In .tftpl files, write literal bash $ as $$ (e.g., $${HOME}) so Terraform does not treat them as template interpolations.

locals {
  install_script = templatefile("${path.module}/scripts/install.sh.tftpl", {
    ARG_FOO = var.foo
    ARG_BAR = var.bar
  })
}

module "coder_utils" {
  source  = "registry.coder.com/coder/coder-utils/coder"
  version = "0.0.1"

  agent_id            = var.agent_id
  module_directory    = "$HOME/.coder-modules/<namespace>/<module-name>"
  display_name_prefix = "My Module"
  icon                = var.icon
  pre_install_script  = var.pre_install_script
  install_script      = local.install_script
  post_install_script = var.post_install_script
  start_script        = var.start_script # optional; omit if the module does not start a process
}

Always expose the scripts output as a pass-through so upstream modules can serialize their own coder_script resources behind this module's install pipeline:

output "scripts" {
  description = "Ordered list of coder exp sync names produced by this module, in run order."
  value       = module.coder_utils.scripts
}

Code Style

  • Every module MUST have .tftest.hcl tests; optional main.test.ts for container/script tests
  • README frontmatter: display_name, description, icon, verified: false, tags
  • Use semantic versioning; bump version via script when modifying modules
  • Docker tests require Linux or Colima/OrbStack (not Docker Desktop)
  • Use tf (not hcl) for code blocks in README; use relative icon paths (e.g., ../../../../.icons/)
  • Never include parameter listings or input/output variable tables in module or template READMEs. This includes workspace parameters declared with coder_parameter. The registry automatically parses the Terraform source and displays parameters in a dedicated tab on registry.coder.com; input/output documentation is also generated from the source. Duplicating these listings in the README is redundant and creates maintenance drift.
  • Usage examples (e.g., a module "..." { } block) and explanations of parameter behavior are encouraged, but not tables or lists enumerating parameters, inputs, or outputs.
  • Script shebangs must be #!/usr/bin/env bash, never #!/bin/bash. NixOS workspaces have only /bin/sh, so the kernel fails the exec before anything runs and the agent reports exit 255 with an empty log — which looks like a broken module, not a missing interpreter.

Variable and output conventions

Order variable blocks: description → type → default → validation → sensitive.

variable "api_key" {
  description = "API key for the service."
  type        = string
  default     = ""
  sensitive   = true
}
  • Mark variables and outputs that hold secrets or tokens sensitive = true.
  • Every output block must have a description.
  • Use count = condition ? 1 : 0 for optional singleton resources. Reserve for_each for maps/sets where resource identity matters.

.tftest.hcl test commands

  • Use command = plan only for assertions on input-derived values (variables, locals computed from inputs).
  • Use command = apply for computed attributes (resource IDs, anything the provider generates), and for nested blocks of set type (they cannot be indexed with [0] under plan).

PR Review Checklist

  • Version bumped via .github/scripts/version-bump.sh if module changed (patch=bugfix, minor=feature, major=breaking)
  • Breaking changes documented: removed inputs, changed defaults, new required variables
  • New variables have sensible defaults to maintain backward compatibility
  • Tests pass (bun run check:terraform, bun run check:typescript, or bun check for everything); add diagnostic logging for test failures
  • README examples updated with new version number; tooltip/behavior changes noted
  • Shell scripts handle errors gracefully (use || echo "Warning..." for non-fatal failures)
  • No hardcoded values that should be configurable; no absolute URLs (use relative paths)
  • If AI-assisted: include model and tool/agent name at footer of PR body (e.g., "Generated with Amp using Claude")