Repository navigation
feat(coder/agent-relay-claude-code)!: make additional_args a list of strings - #1205
Merged
Merged
Conversation
…strings
additional_args was a map of flag to value, which cannot repeat a flag:
a repeated key silently keeps only the last value. It is now a list with
one element per argument, passed in order, so a flag can repeat. Flags
the module sets are still rejected, with or without =value.
BREAKING CHANGE: additional_args changes type from map(string) to
list(string). Rewrite { "--flag" = "value" } as ["--flag", "value"].
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R2TMgCPW8SL8kUCB28L4dt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R2TMgCPW8SL8kUCB28L4dt
This was referenced Oct 23, 2026
Contributor
Module Scorecard Check
|
| Theme | Before | After |
|---|---|---|
| Presentation & Onboarding | 12 / 17 | 6 / 17 |
| Integration | 0 / 25 | 0 / 25 |
| Credential Hygiene | 20 / 20 | 20 / 20 |
| Restricted-Environment | 12 / 20 | 12 / 20 |
| Engineering Quality | 10 / 10 | 10 / 10 |
| Overall | 59 / 100 | 52 / 100 |
Full scorecard for this PR
| Presentation & Onboarding | Agent Integration | Credential Hygiene | Restricted-Environment Readiness | Engineering Quality | Overall |
|---|---|---|---|---|---|
| 6 / 17 | 0 / 25 | 20 / 20 | 12 / 20 | 10 / 10 | 52 / 100 |
Drilldown
Presentation & Onboarding — 6 / 17
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Configuration-mode examples | 12 | 6 | Primary example (default install_cli = true) and additional_args example are both present with sensible defaults. However, the "baked-in CLI" mode (install_cli = false), which the README explicitly calls "the recommended and fastest path," has no dedicated example block—it is only mentioned in a code comment and the variable description. |
| Visual preview | 5 | 0 | No image, GIF, or video embedded in the README. The frontmatter icon field is a module icon, not a preview of the module in action. |
Agent Integration — 0 / 25
| Criterion | Max | Score | Notes |
|---|---|---|---|
| AI governance | 10 | 0 | No mention of Coder AI Gateway or Agent Firewall anywhere in the README or code. The module relies on Agent Relay's work-order JWT for auth but does not document how Coder governs routing or policy enforcement via AI Gateway or Agent Firewall. |
| Dashboard entry point | 5 | 0 | No coder_app resource declared or documented. The module is a headless runner dispatched by Agent Relay; there is no user-facing dashboard entry point. |
| Session continuity | 5 | 0 | No documentation of resuming an existing session across reconnects or relaunches. The agent_relay_session_id parameter is for Agent Relay's internal deduplication, not user-facing session resume. No mention of tmux, screen, boo, or native resume. |
| Managed configuration | 5 | 0 | No documented support for managed MCP, settings, policies, or workdir config. base_dir sets a checkout directory but is not a managed-configuration mechanism. No MCP server config, no settings file, no policy enforcement. |
Credential Hygiene — 20 / 20
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Secrets marked sensitive | 16 | 16 | agent_relay_credential uses mask_input = true in its styling (confirmed in main.tf and asserted in main.tftest.hcl). README examples contain no inline secrets—only agent_id = coder_agent.main.id and structural parameters. |
| Non-hardcoded auth path | 4 | 4 | The credential is a single-use work-order JWT injected by Agent Relay at dispatch time. README states: "Agent Relay sets this when it dispatches the workspace; a human never fills it in." No raw key is pasted into the template. |
Restricted-Environment Readiness — 12 / 20
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Mirrorable artifact source | 5 | 0 | The install URL https://claude.ai/install.sh is hardcoded in install.sh.tftpl. No module input variable overrides this download URL. cli_binary changes the expected binary path, not the source URL. No variable exists to point the installer at an internal mirror. |
| Bring-your-own binary | 10 | 10 | install_cli = false disables the download entirely. The install script checks command -v ${cli_binary} and skips if present. README: "Bake the CLI into the image and set this to false for faster workspaces." The start script also adds ~/.local/bin to PATH unconditionally so a baked-in CLI is found. |
| Egress transparency | 3 | 0 | No dedicated README section enumerating external endpoints. claude.ai/downloads.claude.ai (install) and Anthropic API (runtime) are inferable from code and variable descriptions but not listed in a network/air-gapped section. |
| Runs without sudo | 2 | 2 | All three scripts (install.sh.tftpl, start.sh.tftpl, status.sh.tftpl) operate exclusively under $HOME paths. No sudo invocation anywhere. The installer writes to ~/.local/bin; the runner writes to $HOME/.coder-modules/.... |
Engineering Quality — 10 / 10
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Input quality | 6 | 6 | All 8 variables have clear descriptions and sensible defaults. cli_binary has a regex validation preventing shell metacharacters. exit_if_unused_min validates non-negative integer. additional_args validates non-empty, no newlines, and rejects reserved flags (--capacity, --base-dir, --exit-if-unused-min, --exec-path). |
| Test coverage | 4 | 4 | main.tftest.hcl covers parameter contract (names, ephemeral flags, styling/masking), runner wiring, install CLI behavior, additional_args encoding, path overrides, and serving_log_pattern safety. main.test.ts runs 10+ end-to-end tests in containers with stub binaries: idle state, terminal-state persistence, missing-CLI detection, BYO-CLI discovery, download skip, file layout, detached runner with permissions wrapper, additional_args ordering, exit-code recording, custom paths, and shell-injection resistance of serving_log_pattern. |
Overall — 52 / 100
Raw 48 / 92 → round(48 / 92 × 100) = 52
Tip
You can run this locally by telling your agent: "review this module against .github/scorecard/SCORECARD.md".
Scored against SCORECARD.md with solstice-1. Language-model scores are advisory.
35C4n0r
approved these changes
Oct 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Changes
additional_args, added in v0.3.0 (coder/registry#1198), from amap(string)of flag to value into alist(string)with one element per argument.Why: a map can't repeat a flag, as 35C4n0r pointed out on coder/registry#1202. Worse, Terraform doesn't reject a repeated key:
{ "--header" = "a", "--header" = "b" }silently becomes{ "--header" = "b" }, so the first value disappears without an error. A list also reads like the command line, can be pasted from--help, and keeps the order you write.claude self-hosted-runneras exactly one argument, in the order written, after the flags the module sets and before--exec-path. As before, it's base64-encoded into the start script and shell-quoted withprintf %qinto the supervisor, so spaces, quotes,$VARand backticks are never split or expanded.--capacity,--base-dir,--exit-if-unused-min,--exec-path. Each element is checked with any=valuestripped, so--capacity=2is caught too. The keep-in-sync comments inmain.tfandstart.sh.tftplare unchanged.Type of Change
Module Information
Path:
registry/coder/modules/agent-relay-claude-codeNew version:
v0.4.0Breaking change: [x] Yes [ ] No —
additional_argschanges type frommap(string)tolist(string). To migrate, rewrite{ "--flag" = "value", "--bare" = "" }as["--flag", "value", "--bare"]. The module is pre-1.0, so this ships as a minor bump; v0.3.0 has only been out a few hours, and its tag is left as it is.Testing & Validation
terraform test, 12 passed. Updated: empty default; base64 encoding of a repeated flag in order; rejecting a newline; rejecting--capacity=2.main.test.tsupdated to cover a repeated flag with spaces and quotes. It can't run here because the runner has no Docker daemon.bun fmt);./scripts/terraform_validate.shpassesterraform consoleand ran it against a stubclaude. The argv wasself-hosted-runner --capacity 1 --base-dir … --exit-if-unused-min 10 --name <two words $HOME \id`> --name <it's "q"> --verbose --exec-path …`, with each element unchanged and in order.Related Issues
Follows up the review on coder/registry#1202.
Generated by Claude Code