Repository navigation
feat(registry/coder/modules)!: receive Agent Relay credentials as workspace secrets - #1215
Conversation
…kspace secrets Agent Relay now delivers the work order and the Cursor session token as an ephemeral workspace secret the agent injects into the environment. Both modules drop the agent_relay_credential parameter and its coder_env copy, so the credential never reaches the build's parameters or Terraform state. The dispatched output reads agent_relay_delivery_id. Both modules move to 1.0.0, which needs Coder 2.39.0 and Agent Relay v0.14.0; older deployments stay on 0.5.0 (Claude Code) or 0.8.0 (Cursor). Pairs with coder/agent-relay#119. Based on #1173 by Emyrk.
|
/coder-agents-review Please limit the review to P0, P1, and P2 issues only. |
|
Chat: Review posted | View chat Review historydeep-review v0.13.0 | Round 3 | Last posted: Round 3, 4 findings (4 P2), APPROVE. Review Finding inventoryFindings
Contested and acknowledgedNone. Round logRound 1Full panel (round 1). Netero: no findings. Panel: Kurapika, Melody, Ging-TS, Pariston, Mafuuu, Bisky, Hisoka, Razor, Leorio, Gon, Mafu-san, wildcard Zoro (random draw). Author limited the review to P0-P2 in IC_kwDOOVbRAM8AAAABgPF1og, so only P0-P2 are posted. CRF-1/CRF-2: reviewers split P2 (Mafuuu, Mafu-san) vs P3 (seven others). Higher wins: the PR's Expected Output states the opposite of the shipped behavior for a public output of a major version, and the carry-forward was confirmed in coder/coder CRF-11 to CRF-22 downgrade gate. Keep-argument: Gon's persona fixes comment bloat at P2, and the delivery fact is repeated about six times per module, so the next delivery change can leave stale copies, as happened with the removed parameter's idle message. Downgrade reason: the comments are accurate; the consequence is maintenance cost, which the severity scale rates P3 (rough edge), not P2 (wrong but survivable). Below the author's limit, so not posted. Reviewed against 05f627d..3d5d08c. Round 2Fix round (976bc64 edits two output descriptions and adds one tftest run per module; no files added, no signature change). Churn guard: PROCEED, CRF-1 and CRF-2 addressed. Netero: no findings, CRF-5 verified non-vacuous. Panel: Mafuuu, Mafu-san, Melody, Pariston, Hisoka, Razor, Bisky, Leorio, Gon (all raised CRF-1/CRF-2 or required by floor/test changes), wildcard Kite (random draw). New agents for all; no contested findings. CRF-1, CRF-2: verified fixed by eight reviewers; output descriptions, tests and PR body agree on "created by Agent Relay". CRF-30/CRF-31: CRF-26 was out of scope in R1 because line 42 is unchanged. 976bc64 changed the precondition: the module now documents a restart build as belonging to a relay-created workspace, and the PR's Expected Output states both that and that a restart prints "created manually, not by Agent Relay", plus line 43 (edited in this PR) tells the person to delete the workspace. Mafu-san P2, Leorio P3, Pariston kept OOS. Keep-argument for P2 written first: the log line is false on a path the PR now documents and the PR text contradicts itself. Lower argument: only log text, state handling is correct. Higher wins under disagreement. CRF-34 to CRF-37 downgrade gate. Keep-argument: Gon's persona fixes bloat at P2 and the fact is now written four more times. Downgrade reason: text is accurate; consequence is maintenance cost (P3), consistent with R1 handling. CRF-6 re-raised at P3 with new evidence (dispatched now depends on the parameter persisting); still below limit. Reviewed against 3d5d08c..976bc64. Round 3Fix round (ea58fba changes two echo lines per start script, the README About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
Module Scorecard Check
|
| Theme | Before | After |
|---|---|---|
| Presentation & Onboarding | 12 / 17 | 12 / 17 |
| Integration | 7.5 / 25 | 7.5 / 25 |
| Credential Hygiene | 12 / 20 | 20 / 20 |
| Restricted-Environment | 12 / 20 | 12 / 20 |
| Engineering Quality | 10 / 10 | 10 / 10 |
| Overall | 58 / 100 | 67 / 100 |
Full scorecard for this PR
| Presentation & Onboarding | Agent Integration | Credential Hygiene | Restricted-Environment Readiness | Engineering Quality | Overall |
|---|---|---|---|---|---|
| 12 / 17 | 7.5 / 25 | 20 / 20 | 12 / 20 | 10 / 10 | 67 / 100 |
Drilldown
Presentation & Onboarding — 12 / 17
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Configuration-mode examples | 12 | 12 | README shows distinct documented examples for the major config axes: base usage with install_cli, additional_args ordering/escaping, release_idle_session_min override, and health_port override, each with sensible defaults called out inline. |
| Visual preview | 5 | 0 | No image, GIF, or video anywhere in the README. |
Credential Hygiene — 20 / 20
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Secrets marked sensitive | 16 | 16 | The module deliberately never handles the actual secret (the work-order JWT) in Terraform at all — README's Requirements section explains it arrives as an ephemeral workspace secret (SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET) injected by the agent, never a coder_parameter/coder_env, confirmed by a dedicated test ("sets no coder_env for the work order's variable"). No inline secrets appear in any README example. |
| Non-hardcoded auth path | 4 | 4 | README explicitly documents the ephemeral-secret delivery path as the alternative to pasting a raw JWT into the template, with an upgrade/compat path across Agent Relay versions. |
Restricted-Environment Readiness — 12 / 20
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Mirrorable artifact source | 5 | 0 | cli_binary only overrides the binary path/name, not the download URL. No variable overrides https://claude.ai/install.sh; it is hardcoded in install.sh.tftpl. |
| Bring-your-own binary | 10 | 10 | install_cli = false fully disables the download; README documents it explicitly ("Bake the CLI into the image and set this to false for faster workspaces") and it's test-covered ("expecting '%s' to be in the image"). |
| Egress transparency | 3 | 0 | No dedicated network/egress/air-gapped section in the README. The install URL isn't even named in the README text (only in script comments), let alone enumerated with other endpoints. |
| Runs without sudo | 2 | 2 | None of cleanup.sh.tftpl, install.sh.tftpl, start.sh.tftpl, status.sh.tftpl, or session_attached.sh.tftpl invoke sudo anywhere; all run as the invoking user. |
Engineering Quality — 10 / 10
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Input quality | 6 | 6 | Every variable has a clear, detailed description; sensible defaults throughout; strong validation blocks (cli_binary regex, exit_if_unused_min, release_idle_session_min 0–10080, health_port 0–65535, additional_args reserved-flag and newline checks). |
| Test coverage | 4 | 4 | Extensive .tftest.hcl covering parameter contract and script-rendering business logic, plus a thorough main.test.ts exercising install/start/cleanup lifecycle end-to-end in containers (stale PID detection, idle release, wrapper permission enforcement, etc.). |
Agent Integration — 7.5 / 25
| Criterion | Max | Score | Notes |
|---|---|---|---|
| AI governance | 10 | 0 | No mention of Coder AI Gateway or Agent Firewall anywhere in the README; this module integrates with Anthropic's own "Agent Relay," not Coder's governance surfaces. |
| Dashboard entry point | 5 | 0 | No coder_app resource or documentation of one; README explicitly warns against publishing the health port via coder_app or a Service. |
| Session continuity | 5 | 5 | "Idle session release" section explicitly documents native session resume: an idle session is parked server-side and "resumes on a fresh runner" on the user's next message. |
| Managed configuration | 5 | 2.5 | --permission-mode bypassPermissions is documented as an enforced policy, and idle/capacity timing is configurable, but these are hardcoded behaviors rather than admin-managed settings, and the base_dir (workdir) variable is never shown or documented in the README itself — only in the Terraform variable description. |
Overall — 67 / 100
Raw 61.5 / 92 → round(61.5 / 92 × 100) = 67
coder/agent-relay-cursor: 56 → 62
✅ Score improvement: 56 → 62 (+6).
| Theme | Before | After |
|---|---|---|
| Presentation & Onboarding | 6 / 17 | 6 / 17 |
| Integration | 10 / 25 | 7.5 / 25 |
| Credential Hygiene | 12 / 20 | 20 / 20 |
| Restricted-Environment | 13.5 / 20 | 13.5 / 20 |
| Engineering Quality | 10 / 10 | 10 / 10 |
| Overall | 56 / 100 | 62 / 100 |
Full scorecard for this PR
| Presentation & Onboarding | Agent Integration | Credential Hygiene | Restricted-Environment Readiness | Engineering Quality | Overall |
|---|---|---|---|---|---|
| 6 / 17 | 7.5 / 25 | 20 / 20 | 13.5 / 20 | 10 / 10 | 62 / 100 |
Drilldown
Presentation & Onboarding — 6 / 17
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Configuration-mode examples | 12 | 6 | Main usage example, additional_args example, and management_addr override example are all shown in code blocks. install_cli = false is only noted inline as a comment, and computer_use/share_desktop (a genuinely distinct mode) are described only in prose under Requirements with no accompanying code example. Partial coverage of major modes. |
| Visual preview | 5 | 0 | No image, GIF, or video anywhere in the README. |
Agent Integration — 7.5 / 25
| Criterion | Max | Score | Notes |
|---|---|---|---|
| AI governance | 10 | 0 | No mention of Coder AI Gateway or Agent Firewall anywhere in the README; "Agent Relay" is a distinct dispatch mechanism, not the governed-gateway feature the rubric asks about. |
| Dashboard entry point | 5 | 5 | coder_app.cursor_web and coder_app.cursor_desktop are conditionally created and documented in the README's main section with clear launch-behavior descriptions (web URL / deep link). |
| Session continuity | 5 | 2.5 | The supervisor/worker are explicitly detached (setsid nohup) and documented to outlive the agent process, so a running session can survive an agent reconnect. However, the README states a credentialed agent start always spins up a fresh worker ("An agent start with a credential therefore starts a fresh worker, even after a terminal state"), and there is no explicit framing of this as "resuming a session across reconnects" — it's documented as process-supervision robustness, not session continuity per se. |
| Managed configuration | 5 | 0 | No documented MCP, settings, policy, or workdir configuration management; agent_relay_cursor_repo_url only documents that the template must clone the repo, not that this module manages workdir/config. |
Credential Hygiene — 20 / 20
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Secrets marked sensitive | 16 | 16 | The worker token never becomes a module variable or Terraform state value — it arrives as an ephemeral workspace secret the agent injects as AGENT_RELAY_CURSOR_TOKEN, extensively documented in "Worker credential" and reinforced by a dedicated test ("sets no coder_env for the worker token's variable"). No README example shows an inline/placeholder secret. |
| Non-hardcoded auth path | 4 | 4 | "Worker credential" section explicitly documents the service-account key/session-token/ephemeral-secret flow that avoids ever pasting a raw key into the template. |
Restricted-Environment Readiness — 13.5 / 20
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Mirrorable artifact source | 5 | 0 | install.sh.tftpl hardcodes curl https://cursor.com/install -fsSL | bash. No module variable overrides this download URL (cli_binary only overrides the installed binary's path/name, not the install source). |
| Bring-your-own binary | 10 | 10 | install_cli = false fully disables the download, documented in both the README ("Bake the CLI into the image and set this to false") and install.sh.tftpl's no-op branch; cli_binary lets the path be overridden too. |
| Egress transparency | 3 | 1.5 | No dedicated network/egress/air-gapped section exists. Endpoints are mentioned only scattered across examples/prose (cursor.com/install, cursor.com/agents/<id>, cursor://... deep link, linked Cursor docs) — per calibration this caps at half. |
| Runs without sudo | 2 | 2 | None of cleanup.sh.tftpl, install.sh.tftpl, start.sh.tftpl, status.sh.tftpl, or session_attached.sh.tftpl invoke sudo; all operate as the workspace user. |
Engineering Quality — 10 / 10
| Criterion | Max | Score | Notes |
|---|---|---|---|
| Input quality | 6 | 6 | Every variable has a clear description and sensible default; several carry real validation blocks (cli_binary shell-safety regex, management_addr host:port + port-range checks, share_desktop requires computer_use, additional_args rejects reserved flags/newlines, serving_log_pattern rejects empty string). |
| Test coverage | 4 | 4 | main.tftest.hcl thoroughly covers business logic (parameter contract, script wiring, flag rendering, generation/rotation logic) and main.test.ts runs genuine end-to-end container-based lifecycle tests (idle/working/done/orphaned states, SIGTERM/SIGKILL escalation, pid-reuse protection, injection-safety of pool names). |
Overall — 62 / 100
Raw 57 / 92 → round(57 / 92 × 100) = 62
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.
There was a problem hiding this comment.
This PR moves the Agent Relay credential from a build parameter to a workspace secret in both modules and bumps them to 1.0.0. 2 P2 findings, both about what dispatched now reads. As requested, 21 findings at P3 or lower are not posted; most are comment duplication and wording, plus the new guard tests checking coder_env names rather than the absence of the agent_relay_credential parameter.
Out of scope (needs a ticket or explicit acceptance by a human):
- coder/coder
agent/agent.go:1598: a secret with an env name reaches every process the agent starts (SSH, apps, other scripts), so the Cursor token is in more environments than the worker needs;file_pathdelivery would avoid that but needs a change to the relay's request. test/test.ts:236:runTerraformApplyclears only someCODER_*variables, soterraform testandbun testfail withCODER_WORKSPACE_TEMPLATE_ID is requiredwhen run inside a Coder workspace.registry/coder/modules/agent-relay-claude-code/start.sh.tftpl:42andagent-relay-cursor/start.sh.tftpl:57: "This workspace was created manually" is printed when a person restarts a relay-created workspace, so the start log is wrong on that path.registry/coder/modules/agent-relay-claude-code/main.test.ts:182and the Cursor equivalents: test names still say "credential" while the module text says work order or worker token.tsconfig.json:6:"moduleResolution": "node"is deprecated in TypeScript 6.0, so type checks fail after a move to 6.x.
Notes:
README.md:54in both modules: Coder 2.39.0 and Agent Relay v0.14.0 are not tagged yet (latest are v2.38.3 and v0.13.0). Publishing 1.0.0 before both releases gives templates a version that no released relay can serve.
🤖 This review was automatically generated with Coder Agents.
…gent Relay The delivery id is not ephemeral, so a person's restart of a relay-created workspace keeps dispatched true. That is the intended meaning; the description says so and a test covers the true case.
|
/coder-agents-review Please limit the review to P0, P1, and P2 issues only. |
There was a problem hiding this comment.
Re-review of 976bc64, which documents dispatched as "Agent Relay created this workspace" and adds a true-case test in both modules. Both round-1 dispatched findings are fixed. 2 new P2 findings: the start step's idle message now contradicts that meaning on a restart. As requested, 9 findings at P3 or lower are not posted; the main one is that the agent_relay_delivery_id description in the Claude Code module still says "Tracing only" and "Work order that dispatched this build", although dispatched now depends on the value carrying into later builds.
Out of scope (needs a ticket or explicit acceptance by a human):
registry/coder/modules/agent-relay-claude-code/main.tf:166: the dashboard's Duplicate action copies the non-ephemeralagent_relay_session_idandagent_relay_pool(same in Cursor), so a duplicated workspace matches the relay'sparam:dedupe and reconciliation filters for a session it did not create.registry/coder/modules/agent-relay-cursor/main.tf:166: the display name "Agent Relay delivery" labels a value whose description says it is the claimed worker identity.registry/coder/modules/agent-relay-claude-code/README.md:179: thestoppedrow gives "the workspace was created manually" as the cause, which a restarted relay workspace with no work order also produces.
Notes:
- Only the output description records that
dispatchedchanged from false to true on a person's restart between 0.x and 1.0.0; the README Requirements bullet does not, so a template that gates resources on it learns this only from the description.
🤖 This review was automatically generated with Coder Agents.
…n the idle message A restart of a relay-created workspace has no work order, because the secret is ephemeral, so the start script said the workspace was created manually while dispatched on the same build said Agent Relay created it. Say what the build lacks and leave who created the workspace to dispatched. Same change for the Cursor worker token, the idle rows in both READMEs and the test assertions.
…ls-as-secrets-in-registry
|
/coder-agents-review Please limit the review to P0, P1, and P2 issues only. |
There was a problem hiding this comment.
Re-review of ea58fba, which rewrites the idle message in both start scripts, the README idle rows and the matching test assertions. Both idle-message findings are fixed, and there are no new P0 to P2 findings. As requested, 7 findings at P3 or lower are not posted. The main one: the rewritten README idle rows say a person's restart produces idle, but the start step writes idle only when no state file exists, so a restart of a relay workspace keeps done or failed (the test "keeps terminal state across a restart without a credential" asserts this); status.sh.tftpl:7 in both modules also still defines idle as "created manually".
🤖 This review was automatically generated with Coder Agents.
…e-agent-relay-credentials-as-secrets-in-registry # Conflicts: # registry/coder/modules/agent-relay-claude-code/README.md # registry/coder/modules/agent-relay-claude-code/main.test.ts # registry/coder/modules/agent-relay-claude-code/start.sh.tftpl # registry/coder/modules/agent-relay-cursor/README.md # registry/coder/modules/agent-relay-cursor/main.test.ts # registry/coder/modules/agent-relay-cursor/start.sh.tftpl
…ay-credentials-as-secrets-in-registry' into bobby/relay-75-receive-agent-relay-credentials-as-secrets-in-registry
TL;DR
Agent Relay hands each workspace a short-lived credential: the Anthropic work order for Claude Code, or the Cursor session token. Today both modules receive it as the
agent_relay_credentialbuild parameter, which means it is stored on the build, sent to the provisioner, kept in Terraform state and readable through the API. With coder/agent-relay#119 the relay sends it as an ephemeral workspace build secret instead, and the Coder agent puts it in the start script's environment. This PR makes both modules receive it that way: the credential parameter and thecoder_envthat copied it are removed, the start scripts keep reading the same environment variable, anddispatchednow reads the delivery id. Both modules move to 1.0.0. A template on 1.0.0 needs Coder 2.39.0 and Agent Relay v0.14.0 or newer; older deployments stay on 0.5.0 (Claude Code) or 0.8.0 (Cursor).Based on #1173 by @Emyrk, rewritten on top of current
mainso the Cursor token-file handoff from #1182 and the inputs added since are kept.Refs https://linear.app/codercom/issue/RELAY-75/receive-agent-relay-credentials-as-secrets-in-registry-modules
How to navigate this PR
main.tffiles, a third is tests, and a third is README text. The two start scripts change by one message and two comments each.registry/coder/modules/agent-relay-claude-code/main.tf: the header comment explains the new path. Check that only the credential parameter block and therunner_environment_secretenv resource are gone, and thatoutput "dispatched"now readsagent_relay_delivery_id.registry/coder/modules/agent-relay-cursor/main.tf: the same three changes. The comment abovecursor_agent_worker_idkeeps the sentence from fix(coder/agent-relay-cursor): read the session token from a file #1182 about the token file.registry/coder/modules/agent-relay-claude-code/start.sh.tftplandregistry/coder/modules/agent-relay-cursor/start.sh.tftpl: only the idle message and two comments change. The check for the environment variable, and for Cursor theworker-tokenfile,unset,--auth-token-fileandrm -flines, are as onmain.main.tftest.hcl: the credential assertions are removed and thedispatchedmessage changes. Nothing else in those files moves.main.test.ts: one comment and one new test each. The new test is the guard against acoder_envfor the variable coming back.README.md: the Requirements bullet with the version numbers, the Parameters sentence, the Cursor "Worker credential" delivery sentence, theidlerows and the version in every example.main.test.tsenforces that forcoder_env.Implementation
registry/coder/modules/agent-relay-claude-code/main.tfagent_relay_credentialparameter and thecoder_envthat copied it intoSELF_HOSTED_RUNNER_ENVIRONMENT_SECRET. The agent now sets that variable from the workspace secret, and acoder_envof the same name would be overridden by it anyway.dispatchedreadsagent_relay_delivery_id, which the relay stamps on every build it makes and a person never fills in. The delivery id is not ephemeral, so the output stays true after a person restarts a relay-created workspace. That is deliberate: the output says who created the workspace, and the start script says whether the current build has a work order. On 0.x the output also went false on such a restart, because it read the ephemeral credential.ephemeralflags andordervalues are unchanged, because the relay reads them back.registry/coder/modules/agent-relay-cursor/main.tfAGENT_RELAY_CURSOR_TOKEN. The comment keeps the warning that the token is neverCURSOR_API_KEY.start.sh.tftpldoneorfailedstate.worker-tokenowner-only, unsets the variable in the supervisor and passes--auth-token-file. feat(registry/coder/modules): receive Agent Relay credentials as workspace secrets #1173 predates fix(coder/agent-relay-cursor): read the session token from a file #1182 and would have put the token back on the command line, which is why this PR is written againstmainrather than rebased from that branch.main.tftest.hcldispatchedassertion keeps its check and gets a new message.main.test.tscoder_envin the result sets the variable. It reads the applied state, so it fails for acoder_envof that name under any resource name. It lives here rather than in the Terraform tests becauseterraform testcan only reference resources by address and cannot notice a new one.README.md--auth-token-file, the claim-bound lifetime and the 7-day backstop stay as fix(coder/agent-relay-cursor): read the session token from a file #1182 wrote them.version =example is 1.0.0. The PR carriesversion:majorbecause a template on this version cannot be served by a relay that still sends the parameter, even though no input changes.Expected Output
SELF_HOSTED_RUNNER_ENVIRONMENT_SECRETorAGENT_RELAY_CURSOR_TOKENfrom the workspace secret before the start script runs. The runner or worker starts exactly as on 0.5.0 and 0.8.0.dispatchedis true on every build of a workspace the relay created, including a stop and start by a person, and false on every build of a workspace a person created.doneorfailedstate, and exits 0.--auth-token-fileand never the token, the file is mode 0600, the worker's environment has noAGENT_RELAY_CURSOR_TOKEN, and the file is deleted when the worker exits.missing parameter "agent_relay_credential"and builds no workspace. Pinning the template back to 0.5.0 or 0.8.0 recovers the pool.dispatchedoutput reads false on relay builds, because it still reads the empty parameter.Testing
registry/coder/modules/agent-relay-claude-code/main.tftest.hclandregistry/coder/modules/agent-relay-cursor/main.tftest.hclparameter_contractchecks every remaining parameter's name,ephemeralflag and disabled styling, and no longer mentions the credential.runner_wiringandworker_wiringcheck the remaining env resource names and thatdispatchedis false on a plan with no parameter values.dispatched_true_when_delivery_stampedoverrides the delivery id and checks thatdispatchedis true.install_cli_*,additional_args_*,health_port_*,release_idle_session_min_*,computer_use_*,share_desktop_*,management_addr_*,cli_binary_rejects_shell,overridden_paths,serving_log_pattern_*) is unchanged.registry/coder/modules/agent-relay-claude-code/main.test.tsandregistry/coder/modules/agent-relay-cursor/main.test.tscoder_envnames in the state. Run againstmain'smain.tfthey fail and list the 0.x env resource, which is the regression they guard.terraform init -upgrade && terraform testin each module directory: 25 passed, 0 failed, in both.bun test main.test.tsin each module directory with Docker running: 25 passed (Claude Code) and 26 passed (Cursor), 0 failed.bun run fmtleaves the files unchanged../.github/scripts/version-bump.sh --ci major origin/mainpasses with both modules at v1.0.0.Manual Test
Run on 2026-10-28 against this branch, with dogfood (
https://dogfood.cdr.dev, amainbuild from the same day, 319 commits past the first commit of the workspace secrets stack) as the Coder deployment and a relay built from coder/agent-relaymainat550f96e(two commits past the #119 merge) in the workspacebobby/Bobby-Agent-Relay, serving the pooldemo-runner-bobbythat backs theBobby-Testingenvironment at claude.ai/code. The 1.0 module is not published, so the run pushed non-active versions of dogfood'sagent-relay-template-dockerwhose modulesourcepoints at this branch over git, and pinned the pool to them with the relay'stemplate_versionsetting. Every workspace below is a real docker container built by dogfood on one of those versions, and every session is a real Claude Code session answering a real prompt. Nothing was seeded, and the template's active version was never changed.The run has two halves. The first is the same shape as the #119 run: the relay in secrets mode builds a workspace on the 1.0 module and a session is served inside it. The second is what only the 1.0 module can show: a build with no credential parameter at all,
dispatchedreading the delivery id, the new idle message on a manual restart, a relay that still requires the parameter refusing the template at startup, and the 0.2.0 module on the same relay for comparison. 16 of 16 scenarios passed.mainwith #119, dogfood version and date, two template versions pushed non-active and pinned from the poolcoder_envto carry it; the runner startsdispatchedreads true on a relay buildcoder_env, nothing in the provisioner logdispatchedstays true, the new idle message prints; then Anthropic redelivers and the relay rebuilds in placedispatchedfalsemainFacts worth knowing from the run:
agent_relay_credential=row (present and empty on 0.2.0, absent on 1.0),agent_relay_dispatched(falseon 0.2.0,trueon 1.0) and the provisioner log (two lines for the credential parameter and onerunner_environment_secretresource on 0.2.0, none on 1.0). The runner served in both.dispatchedtrue, and the relay rebuilt the workspace in place when the work order came back. Scenario 10's restart made a build with no secret, an emptyagent_relay_attempt, the sameagent_relay_delivery_idand sodispatchedstilltrue, which is the "created by Agent Relay" meaning settled in review. The reaper left the idle workspace alone, becauseidleis not a terminal state. Eleven minutes later Anthropic redelivered the session withspawn_reason=runner_lostandattempt=1, and the relay logged "existing workspace is not serving the session, rebuilding", made a start build with a fresh secret and reached handoff in nine seconds. That is the rebuild row of the design, observed without being staged.976bc64; the branch hadea58fbaon top, which rewrites the idle message, and the headaff46ceis a merge ofmainwith no Agent Relay change. Every observation holds for the head except the exact idle text in scenario 10, so scenario 15 pushed a third version fromaff46ceand repeated the restart: the head's two lines printed.-develversions, so a relay with feat(local-windows-rdp): local Windows RDP using coder desktop #119 is always in secrets mode there and parameter mode cannot be staged.4f9a391, the lastmainbefore the merge, has the same contract as every released relay (always requiresagent_relay_credential) and has thetemplate_versionpin, so it could be pointed at the non-active 1.0 version. Scenario 12 is that build refusing.0fd07754f5,9f41c06fcd,7f6ce9c8ab), and CI replaced the template's active version (250477etod462b27) at 19:41 UTC. The pinned pool noticed neither, and no step of the run activated a version.wsstatushelper from the feat(local-windows-rdp): local Windows RDP using coder desktop #119 run had a bug. Its jq path looked for.keyand.valueon an agent metadata item, which the API nests as.description.keyand.result.value, so it printed an empty line. The API was fine; the helper was fixed in place. The feat(local-windows-rdp): local Windows RDP using coder desktop #119 run'swsstatuslines deserve a second look for the same reason.1. Environment
The relay clone, config and Anthropic environment key from the #119 run were still in the workspace; only
/tmphad been cleared by a rebuild. The clone was moved tomainwith #119 merged, the config was given the pin, and two template versions were pushed next to the active one:relay75-976bc64with both modules from this branch over git, andrelay75-0xwith the active version's 0.2.0 and 0.4.0 modules, both with onecoder_metadataitem added so the module'sdispatchedoutput is visible through the API.The push's own plan is an early look at the change, before any workspace exists: the 1.0 version plans three
coder_envresources in the module and norunner_environment_secret; the 0.x version plansrunner_environment_secret. Theapi,buildparams,buildsecrets,wsmeta,wsstatus,buildlogsandagentloghelpers used below are thincurlandjqwrappers around the dogfood API using the workspace's own session token.2. The relay starts in secrets mode and verifies the 1.0 template
"template verified, pool serving" is the first thing this PR is responsible for: in secrets mode the contract does not require
agent_relay_credential, the 1.0 module declares the other seven stamped parameters, and a template with no credential parameter at all passes. No "from before build secrets" warning either; scenario 13 shows that line on the 0.2.0 version. The status page athttp://localhost:2119loaded without a login, headeragent-relay v0.12.0-26-g550f96e, the pool serving with live 0.Screen capture: the status page with the pool serving and nothing in flight
3. A session is dispatched on the pinned 1.0 version
A new session at claude.ai/code in
Bobby-Testing, with the prompt "print the hostname, then the output ofenv | grep -c SELF_HOSTED_RUNNERand ofcoder version".Handoff 24 seconds after the build was created, inside the 300 s budget, with no stall and no rebuild. The build is on the pinned version, not the active one.
4. The build has no credential parameter row
Thirteen rows, the template's six and the module's seven, and no
agent_relay_credentialrow. The #119 run on 0.2.0 had a fourteenth, empty row here: coderd keeps only the parameters the version declares, and 1.0 declares none by that name.5. The build carries the secret
This is the relay's half of the contract, verified in the #119 run and repeated here because it is now the only path the work order has. The secret was created between the relay's "resolved session owner" and "workspace build created" lines. No surface returns a value.
6. The secret reaches the start script
On 0.2.0 the same variable arrived through the agent overriding the module's empty
coder_env. On 1.0 there is nocoder_envof that name anywhere in the build (scenarios 1 and 9), so the secret is the only source. The three remaining module variables are present next to it, which shows the agent applied the manifest's env and the secret in the same pass.7. `dispatched` reads true on a relay build
The output now reads
agent_relay_delivery_id, which the relay stamps on every build. On the 0.2.0 module in secrets mode the same item readsfalse(scenario 13), because that output reads the credential parameter the relay no longer sends.Screen capture: the workspace page, with the item on the container's metadata strip and the agent's Session item serving
8. The session is served
The runner answered inside the dispatched workspace: hostname
cc-038ddde2-227f-428b-b8c3-c7131, threeSELF_HOSTED_RUNNERlines in the session's environment,coder versionv2.38.3-devel+0fd07754f5. This is the end-to-end proof for the 1.0 module: the work order reached the runner through coderd, the agent manifest and the start script, with no parameter and nocoder_envon the way.Screen capture: the session's answer, the container's hostname and the coder version
Screen capture: the status page with the session in the serving column and the pool live 1
9. Nothing in the build knows the credential
The #119 run on 0.2.0 had two lines for
agent_relay_credentialhere and one morecoder_env. On 1.0 the data source and the env resource do not exist, so Terraform has nothing to refresh or create by those names. The seven parameters and three env resources listed are the positive control that this is the right log.10. A manual restart, and the redelivery that followed
A restart is a stop build and a start build, so the start is build 3. Build 1's secret now carries
cleared_at; build 3 has no secret, because ephemeral secrets are not copied forward and the relay did not make this build.agent_relay_delivery_idis persistent and carries forward, sodispatchedstaystrue;agent_relay_attemptis ephemeral and resets. The start script took the no-credential branch and printed the idle message of976bc64, the commit the template version was built from; scenario 15 shows the head's wording.Screen capture: the workspace page after the restart, Logs open, Session idle, the metadata item still true
Eleven minutes later, with the session page still open at claude.ai, Anthropic redelivered the session because its runner had gone:
The relay never queued a delete: the idle workspace was not reaped, it was rebuilt in place with a fresh secret, a new delivery id and
attempt=1, and the runner served again.11. The session ends
The session was ended from its page at claude.ai/code with the session menu's Release runner.
The runner exited cleanly, the status script reported
done 0, the reaper deleted the workspace on its next sweep, and no nack was sent. The relay log names exactly one workspace and no other owner, which is the pool scope holding: eighteen other owners' workspaces were running on the same template at the start of the run and ten at this point, all changes theirs.Screen capture: the session right after the release, looking for a runner again
12. A relay that still requires the parameter refuses the 1.0 version
Built from
4f9a391, the last agent-relaymainbefore #119 merged, in a worktree, and pointed at the 1.0 version with its own ports, a pool name nothing else uses and a placeholder environment key. The relay from scenario 2 kept running.The authenticate line has no
build_secretsfield, because that field is #119's. Instead of "template verified, pool serving" the pool fails the contract check every 30 s, naming the parameter; the poll loop never starts, so the placeholder key is never sent anywhere and no workspace is built. This is the README's promise to an operator who moves a template to 1.0 before upgrading the relay.13. The 0.2.0 module on the same relay
The same relay, same mode, same secret, other module. The session answered inside the workspace (hostname
cc-038ddde2-0693-4314-90b5-e8610) and was ended and reaped as in scenario 11. Side by side with scenarios 4, 7 and 9:relay75-976bc64)relay75-0x)agent_relay_credentialparameter rowagent_relay_dispatchedtruefalseagent_relay_credentialrunner_environment_secretThat is the whole difference between the two module generations as an operator sees it, and why the README's Requirements bullet is advice rather than a hard break: a template left on 0.x keeps serving on the new relay.
14. The modules' own tests
With
main'smain.tfin place the guard fails on the assertion it was written for, listing the 0.x env resource; the tree is clean afterwards.15. The branch head's idle wording, end to end
A third version from the head,
aff46ce, the pool re-pinned to it, a session dispatched, and the restart from scenario 10 repeated.The same shape as scenarios 3, 4, 7 and 10 on the commit under review, and the two lines the Expected Output section quotes. The runner was then released at claude.ai and the idle workspace deleted by hand, since the reaper does not delete
idle.16. Cleanup
No relay process of either build, no
bobby/cc-*workspace, the config back to its pre-run shape with the key and without the pin, the threerelay75-*versions left non-active (to be archived once 1.0.0 is published), and the work order prefix in neither relay log. The relay log names three workspaces across the whole run, allbobby/cc-*.