From dd3e89cb91e88257fc2da472a2c0c14e325f8537 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Thu, 22 Oct 2026 14:53:52 +0000 Subject: [PATCH 1/3] feat(coder/agent-relay-cursor): add additional_args Add an additional_args map of flag to value, appended to `cursor-agent worker` after the flags the module sets and before the `start` subcommand. Each flag is passed exactly as written (--long or -s, the admin's choice), followed by its value as the next argument; an empty value passes the flag alone. Values reach the CLI verbatim. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01R2TMgCPW8SL8kUCB28L4dt --- .../modules/agent-relay-cursor/README.md | 23 ++++++++++ .../modules/agent-relay-cursor/main.test.ts | 22 ++++++++++ .../coder/modules/agent-relay-cursor/main.tf | 16 +++++++ .../agent-relay-cursor/main.tftest.hcl | 43 +++++++++++++++++++ .../modules/agent-relay-cursor/start.sh.tftpl | 11 +++++ 5 files changed, 115 insertions(+) diff --git a/registry/coder/modules/agent-relay-cursor/README.md b/registry/coder/modules/agent-relay-cursor/README.md index a3fe85ba0..2258d8c11 100644 --- a/registry/coder/modules/agent-relay-cursor/README.md +++ b/registry/coder/modules/agent-relay-cursor/README.md @@ -86,6 +86,29 @@ for example after the idle-release timeout, or when the service account key that minted it is deleted or expires. Its 7-day expiry is a backstop, not a session limit. +## Additional worker arguments + +Use `additional_args` to pass extra flags to `cursor-agent worker`, as a map of +flag to value. They are appended after the flags the module sets and before +the `start` subcommand. Each flag is passed exactly as written, so use `--name` +or `-n` as the CLI expects, followed by its value as the next argument; an +empty value passes the flag alone. Values are passed verbatim, so spaces and +quotes need no escaping. Give a flag that takes an optional value an explicit +one, or it consumes `start`. + +```tf +module "cursor_worker" { + source = "registry.coder.com/coder/agent-relay-cursor/coder" + version = "0.5.0" + agent_id = coder_agent.main.id + # Passed as: --some-flag value --another-flag + additional_args = { + "--some-flag" = "value" + "--another-flag" = "" + } +} +``` + ## Parameters Agent Relay verifies this contract against the template's active version at diff --git a/registry/coder/modules/agent-relay-cursor/main.test.ts b/registry/coder/modules/agent-relay-cursor/main.test.ts index 29f869d42..fe04634f6 100644 --- a/registry/coder/modules/agent-relay-cursor/main.test.ts +++ b/registry/coder/modules/agent-relay-cursor/main.test.ts @@ -419,6 +419,28 @@ describe("agent-relay-cursor", () => { expect(args).toContain("start"); }); + it("appends additional_args before start, one argument per flag", async () => { + const { id, scripts } = await setup({ + additional_args: JSON.stringify({ + "--verbose": "", + "--name": "two words $HOME", + "-t": `it"s`, + }), + }); + await stubAgent(id, 'printf "%s\\n" "$@" >/tmp/agent-args; sleep 30'); + const { start } = await runDispatched(id, scripts); + expect(start.exitCode).toBe(0); + const args = (await readFileContainer(id, "/tmp/agent-args")).split("\n"); + expect(args[0]).toBe("worker"); + expect(args).toContain("--verbose"); + // Values reach the CLI verbatim: no word splitting or expansion. + expect(args[args.indexOf("--name") + 1]).toBe("two words $HOME"); + expect(args[args.indexOf("-t") + 1]).toBe('it"s'); + // The subcommand stays last, after every flag. + expect(args.indexOf("start")).toBeGreaterThan(args.indexOf("-t") + 1); + expect(args[args.indexOf("--pool") + 1]).toBe(POOL_NAME); + }); + it("records the exit code when the worker exits", async () => { const { id, scripts } = await setup(); await stubAgent(id, "exit 3"); diff --git a/registry/coder/modules/agent-relay-cursor/main.tf b/registry/coder/modules/agent-relay-cursor/main.tf index e0387ec43..03e7f18fc 100644 --- a/registry/coder/modules/agent-relay-cursor/main.tf +++ b/registry/coder/modules/agent-relay-cursor/main.tf @@ -104,6 +104,17 @@ variable "serving_log_pattern" { } } +variable "additional_args" { + description = "Additional flags to pass to `cursor-agent worker`, as a map of flag to value. Each flag is passed as written (e.g. `--name` or `-n`) followed by its value as the next argument; an empty value passes the flag alone. Appended after the flags the module sets and before `start`." + type = map(string) + default = {} + + validation { + condition = alltrue([for k, v in var.additional_args : can(regex("^--?[A-Za-z0-9][A-Za-z0-9._-]*$", k)) && !can(regex("\n", v))]) + error_message = "additional_args keys must be flags such as --name or -n (letters, digits, '.', '_', '-'), and values must not contain newlines." + } +} + data "coder_parameter" "agent_relay_session_id" { name = "agent_relay_session_id" display_name = "Agent Relay session" @@ -242,6 +253,10 @@ locals { # tree so one directory holds everything a debugger needs. module_directory = "$HOME/.coder-modules/coder/agent-relay-cursor" + # Render additional_args as argv: each key as written, followed by its + # value as a separate argument unless the value is empty. + additional_args = flatten([for k, v in var.additional_args : v == "" ? [k] : [k, v]]) + install_script = templatefile("${path.module}/install.sh.tftpl", { cli_binary = var.cli_binary install_cli = var.install_cli @@ -255,6 +270,7 @@ locals { share_desktop = var.share_desktop state_file = var.state_file log_file = var.log_file + additional_args = base64encode(join("\n", local.additional_args)) }) } diff --git a/registry/coder/modules/agent-relay-cursor/main.tftest.hcl b/registry/coder/modules/agent-relay-cursor/main.tftest.hcl index 78655fb93..25edd4b03 100644 --- a/registry/coder/modules/agent-relay-cursor/main.tftest.hcl +++ b/registry/coder/modules/agent-relay-cursor/main.tftest.hcl @@ -235,6 +235,49 @@ run "share_desktop_requires_computer_use" { expect_failures = [var.share_desktop] } +run "additional_args_default_empty" { + command = plan + + assert { + condition = local.additional_args == [] && strcontains(local.start_script, "done < <(echo -n '' | base64 -d)") + error_message = "additional_args must default to empty" + } +} + +run "additional_args_encoded" { + command = plan + + variables { + additional_args = { + "--verbose" = "" + "--name" = "it's here" + "-t" = "x" + } + } + + # Map keys sort byte-wise, so --flags come before -f flags. + assert { + condition = local.additional_args == ["--name", "it's here", "--verbose", "-t", "x"] + error_message = "each flag must be passed as written, followed by its value unless the value is empty" + } + + # Base64 keeps quotes in a value from breaking the rendered script. + assert { + condition = strcontains(local.start_script, base64encode(join("\n", local.additional_args))) && !strcontains(local.start_script, "it's here") + error_message = "additional_args must be base64-encoded into the start script" + } +} + +run "additional_args_rejects_bad_flag" { + command = plan + + variables { + additional_args = { "verbose; rm -rf /" = "" } + } + + expect_failures = [var.additional_args] +} + run "install_cli_enabled_by_default" { command = plan diff --git a/registry/coder/modules/agent-relay-cursor/start.sh.tftpl b/registry/coder/modules/agent-relay-cursor/start.sh.tftpl index 172837e8b..f6e784452 100644 --- a/registry/coder/modules/agent-relay-cursor/start.sh.tftpl +++ b/registry/coder/modules/agent-relay-cursor/start.sh.tftpl @@ -23,6 +23,16 @@ token_file="${module_directory}/worker-token" scripts_dir="${module_directory}/scripts" mkdir -p "$scripts_dir" "$(dirname "$state_file")" "$(dirname "$log_file")" +# additional_args, one argument per line. Shell-quoted here because the +# string is expanded into the supervisor below, which must see each +# argument as one word and never evaluate it. +additional_args="" +while IFS= read -r arg || [ -n "$arg" ]; do + if [ -n "$arg" ]; then + additional_args+="$(printf '%q' "$arg") " + fi +done < <(echo -n '${additional_args}' | base64 -d) + write_state() { # Write via a temp file so a reader never sees a partial line. printf '%s\n' "$1" >"$state_file.tmp" @@ -136,6 +146,7 @@ ${cli_binary} worker \\ %{ if share_desktop ~} --share-desktop=view_and_control \\ %{ endif ~} + $additional_args \\ start >"$log_file" 2>&1 & worker_pid=\$! write_state "working \$worker_pid" From bfe5874d81c6db4a93b5304a7dd10c4d91354a12 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Thu, 22 Oct 2026 14:54:49 +0000 Subject: [PATCH 2/3] chore(coder/agent-relay-cursor): bump version to 0.6.0 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01R2TMgCPW8SL8kUCB28L4dt --- registry/coder/modules/agent-relay-cursor/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/registry/coder/modules/agent-relay-cursor/README.md b/registry/coder/modules/agent-relay-cursor/README.md index 2258d8c11..c13a6545c 100644 --- a/registry/coder/modules/agent-relay-cursor/README.md +++ b/registry/coder/modules/agent-relay-cursor/README.md @@ -16,7 +16,7 @@ parameters the relay stamps on each build and runs the Cursor CLI worker. ```tf module "cursor_worker" { source = "registry.coder.com/coder/agent-relay-cursor/coder" - version = "0.5.0" + version = "0.6.0" agent_id = coder_agent.main.id # Downloads the Cursor CLI at start when it is not in the image. Bake @@ -93,18 +93,18 @@ flag to value. They are appended after the flags the module sets and before the `start` subcommand. Each flag is passed exactly as written, so use `--name` or `-n` as the CLI expects, followed by its value as the next argument; an empty value passes the flag alone. Values are passed verbatim, so spaces and -quotes need no escaping. Give a flag that takes an optional value an explicit +quotes need no escaping. Terraform orders map keys alphabetically. Give a flag that takes an optional value an explicit one, or it consumes `start`. ```tf module "cursor_worker" { source = "registry.coder.com/coder/agent-relay-cursor/coder" - version = "0.5.0" + version = "0.6.0" agent_id = coder_agent.main.id - # Passed as: --some-flag value --another-flag + # Passed as: --another-flag --some-flag value additional_args = { - "--some-flag" = "value" "--another-flag" = "" + "--some-flag" = "value" } } ``` From a3a51bd212a33ee6de9e8284c55c97558579e841 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Thu, 22 Oct 2026 14:56:45 +0000 Subject: [PATCH 3/3] feat(coder/agent-relay-cursor): reject flags the module already sets additional_args may not set --pool, --idle-release-timeout, --auth-token-file, --auth-token, --computer-use or --share-desktop. Comments on both sides keep the list in sync with the worker command. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01R2TMgCPW8SL8kUCB28L4dt --- registry/coder/modules/agent-relay-cursor/README.md | 6 ++++-- registry/coder/modules/agent-relay-cursor/main.tf | 11 ++++++++++- .../coder/modules/agent-relay-cursor/main.tftest.hcl | 10 ++++++++++ .../coder/modules/agent-relay-cursor/start.sh.tftpl | 2 ++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/registry/coder/modules/agent-relay-cursor/README.md b/registry/coder/modules/agent-relay-cursor/README.md index c13a6545c..90f6b9e7f 100644 --- a/registry/coder/modules/agent-relay-cursor/README.md +++ b/registry/coder/modules/agent-relay-cursor/README.md @@ -88,12 +88,14 @@ session limit. ## Additional worker arguments -Use `additional_args` to pass extra flags to `cursor-agent worker`, as a map of +Use `additional_args` to pass extra flags to `agent worker`, as a map of flag to value. They are appended after the flags the module sets and before the `start` subcommand. Each flag is passed exactly as written, so use `--name` or `-n` as the CLI expects, followed by its value as the next argument; an empty value passes the flag alone. Values are passed verbatim, so spaces and -quotes need no escaping. Terraform orders map keys alphabetically. Give a flag that takes an optional value an explicit +quotes need no escaping. Terraform orders map keys alphabetically. Flags the module already sets +(`--pool`, `--idle-release-timeout`, `--auth-token-file`, `--auth-token`, +`--computer-use`, `--share-desktop`) are rejected. Give a flag that takes an optional value an explicit one, or it consumes `start`. ```tf diff --git a/registry/coder/modules/agent-relay-cursor/main.tf b/registry/coder/modules/agent-relay-cursor/main.tf index 03e7f18fc..d60e33c13 100644 --- a/registry/coder/modules/agent-relay-cursor/main.tf +++ b/registry/coder/modules/agent-relay-cursor/main.tf @@ -105,7 +105,7 @@ variable "serving_log_pattern" { } variable "additional_args" { - description = "Additional flags to pass to `cursor-agent worker`, as a map of flag to value. Each flag is passed as written (e.g. `--name` or `-n`) followed by its value as the next argument; an empty value passes the flag alone. Appended after the flags the module sets and before `start`." + description = "Additional flags to pass to `agent worker`, as a map of flag to value. Each flag is passed as written (e.g. `--name` or `-n`) followed by its value as the next argument; an empty value passes the flag alone. Appended after the flags the module sets and before `start`." type = map(string) default = {} @@ -113,6 +113,15 @@ variable "additional_args" { condition = alltrue([for k, v in var.additional_args : can(regex("^--?[A-Za-z0-9][A-Za-z0-9._-]*$", k)) && !can(regex("\n", v))]) error_message = "additional_args keys must be flags such as --name or -n (letters, digits, '.', '_', '-'), and values must not contain newlines." } + + # The module already sets these: the pool and idle timeout come from Agent + # Relay's parameters, the token is passed by file so it stays off the + # command line, and desktop flags have their own inputs. Keep this list in + # sync with the `worker` flags in start.sh.tftpl. + validation { + condition = length(setintersection(keys(var.additional_args), ["--pool", "--idle-release-timeout", "--auth-token-file", "--auth-token", "--computer-use", "--share-desktop"])) == 0 + error_message = "additional_args must not set --pool, --idle-release-timeout, --auth-token-file, --auth-token, --computer-use, or --share-desktop; the module sets them (use computer_use and share_desktop for the desktop flags)." + } } data "coder_parameter" "agent_relay_session_id" { diff --git a/registry/coder/modules/agent-relay-cursor/main.tftest.hcl b/registry/coder/modules/agent-relay-cursor/main.tftest.hcl index 25edd4b03..be7cc6ba3 100644 --- a/registry/coder/modules/agent-relay-cursor/main.tftest.hcl +++ b/registry/coder/modules/agent-relay-cursor/main.tftest.hcl @@ -278,6 +278,16 @@ run "additional_args_rejects_bad_flag" { expect_failures = [var.additional_args] } +run "additional_args_rejects_module_flags" { + command = plan + + variables { + additional_args = { "--auth-token" = "secret" } + } + + expect_failures = [var.additional_args] +} + run "install_cli_enabled_by_default" { command = plan diff --git a/registry/coder/modules/agent-relay-cursor/start.sh.tftpl b/registry/coder/modules/agent-relay-cursor/start.sh.tftpl index f6e784452..cde1473dc 100644 --- a/registry/coder/modules/agent-relay-cursor/start.sh.tftpl +++ b/registry/coder/modules/agent-relay-cursor/start.sh.tftpl @@ -136,6 +136,8 @@ write_state() { export PATH="\$HOME/.local/bin:\$PATH" unset AGENT_RELAY_CURSOR_TOKEN +# Keep these flags in sync with the reserved-flag list in additional_args' +# validation in main.tf, so additional_args cannot set them a second time. ${cli_binary} worker \\ --pool "\$AGENT_RELAY_CURSOR_POOL_NAME" \\ --idle-release-timeout "\$AGENT_RELAY_CURSOR_IDLE_RELEASE_TIMEOUT" \\