Skip to content

feat(registry/coder/modules)!: receive Agent Relay credentials as workspace secrets - #1215

Merged
BobbyHo merged 6 commits into
mainfrom
bobby/relay-75-receive-agent-relay-credentials-as-secrets-in-registry
Oct 29, 2026
Merged

BobbyHo merged 6 commits into
mainfrom
bobby/relay-75-receive-agent-relay-credentials-as-secrets-in-registry

Conversation

@BobbyHo

@BobbyHo BobbyHo commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

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_credential build 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 the coder_env that copied it are removed, the start scripts keep reading the same environment variable, and dispatched now 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 main so 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
  • About a third of the diff is Terraform in the two main.tf files, a third is tests, and a third is README text. The two start scripts change by one message and two comments each.
  • Suggested reading order:
    1. registry/coder/modules/agent-relay-claude-code/main.tf: the header comment explains the new path. Check that only the credential parameter block and the runner_environment_secret env resource are gone, and that output "dispatched" now reads agent_relay_delivery_id.
    2. registry/coder/modules/agent-relay-cursor/main.tf: the same three changes. The comment above cursor_agent_worker_id keeps the sentence from fix(coder/agent-relay-cursor): read the session token from a file #1182 about the token file.
    3. registry/coder/modules/agent-relay-claude-code/start.sh.tftpl and registry/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 the worker-token file, unset, --auth-token-file and rm -f lines, are as on main.
    4. Both main.tftest.hcl: the credential assertions are removed and the dispatched message changes. Nothing else in those files moves.
    5. Both main.test.ts: one comment and one new test each. The new test is the guard against a coder_env for the variable coming back.
    6. Both README.md: the Requirements bullet with the version numbers, the Parameters sentence, the Cursor "Worker credential" delivery sentence, the idle rows and the version in every example.
  • The most important check is that nothing in either module reads, copies or outputs the credential any more. The guard test in each main.test.ts enforces that for coder_env.
  • Nothing outside the two modules changes. No input variable is added or removed, so a template needs no edit beyond the version.
Implementation
  • registry/coder/modules/agent-relay-claude-code/main.tf
    • Removed the agent_relay_credential parameter and the coder_env that copied it into SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET. The agent now sets that variable from the workspace secret, and a coder_env of the same name would be overridden by it anyway.
    • dispatched reads agent_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.
    • The header comment says the work order is a workspace secret that nothing in the module can read. The remaining parameters, their ephemeral flags and order values are unchanged, because the relay reads them back.
  • registry/coder/modules/agent-relay-cursor/main.tf
    • The same three changes for AGENT_RELAY_CURSOR_TOKEN. The comment keeps the warning that the token is never CURSOR_API_KEY.
  • Both start.sh.tftpl
  • Both main.tftest.hcl
    • Dropped the five assertions about the credential parameter and the env resource. The dispatched assertion keeps its check and gets a new message.
  • Both main.test.ts
    • One new test applies the module and asserts that no coder_env in the result sets the variable. It reads the applied state, so it fails for a coder_env of that name under any resource name. It lives here rather than in the Terraform tests because terraform test can only reference resources by address and cannot notice a new one.
  • Both README.md
    • Requirements gain a first bullet naming Coder 2.39.0 and Agent Relay v0.14.0, the refusal message an older relay logs, the 0.x version to pin, the upgrade order and the rollback step.
    • The Parameters sentence says the credential is a workspace secret and the template should not set the variable itself.
    • The Cursor "Worker credential" section changes only its delivery sentence. The token file, mode 0600, --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.
    • Every version = example is 1.0.0. The PR carries version:major because a template on this version cannot be served by a relay that still sends the parameter, even though no input changes.
  • Not done on purpose: no transitional version that declares the parameter and also reads the secret. A relay in secrets mode accepts templates with or without the parameter, so 0.5.0 and 0.8.0 already serve that role.
Expected Output
  • On 1.0.0 the workspace create page shows the relay's parameters disabled as before and no "Agent Relay credential" field.
  • A relay build on Coder 2.39.0 or newer with Agent Relay v0.14.0 or newer sets SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET or AGENT_RELAY_CURSOR_TOKEN from the workspace secret before the start script runs. The runner or worker starts exactly as on 0.5.0 and 0.8.0.
  • The credential is not in the build's parameter list, not in Terraform state and not in the dashboard.
  • dispatched is 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.
  • A build with no work order, which is a workspace a person created or a relay-created workspace a person restarted, prints "No work order in this build. Agent Relay delivers one only on the build it dispatches." and then "Idling. If Agent Relay did not create this workspace, delete it." (or "No worker token in this build." for Cursor), keeps any earlier done or failed state, and exits 0.
  • For Cursor, the worker's command line carries --auth-token-file and never the token, the file is mode 0600, the worker's environment has no AGENT_RELAY_CURSOR_TOKEN, and the file is deleted when the worker exits.
  • A relay that still sends the credential as a parameter, which is any relay before v0.14.0 or a v0.14.0 relay against Coder older than 2.39.0, refuses a 1.0.0 template at startup with missing parameter "agent_relay_credential" and builds no workspace. Pinning the template back to 0.5.0 or 0.8.0 recovers the pool.
  • A template still on 0.5.0 or 0.8.0 keeps working on the new relay and Coder. The one visible difference is that its dispatched output reads false on relay builds, because it still reads the empty parameter.
  • Templates in one deployment can be on 0.x and 1.0.0 at the same time and move one at a time.
Testing
  • registry/coder/modules/agent-relay-claude-code/main.tftest.hcl and registry/coder/modules/agent-relay-cursor/main.tftest.hcl
    • parameter_contract checks every remaining parameter's name, ephemeral flag and disabled styling, and no longer mentions the credential.
    • runner_wiring and worker_wiring check the remaining env resource names and that dispatched is false on a plan with no parameter values.
    • New in both: dispatched_true_when_delivery_stamped overrides the delivery id and checks that dispatched is true.
    • Every other run (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.ts and registry/coder/modules/agent-relay-cursor/main.test.ts
    • New: "sets no coder_env for the work order's variable" and "sets no coder_env for the worker token's variable". Each applies the module and asserts the variable is not among the coder_env names in the state. Run against main's main.tf they fail and list the 0.x env resource, which is the regression they guard.
    • "idles when no credential is set" now looks for the first line of the new idle message; it and "keeps terminal state across a restart without a credential" cover the build with no work order. "starts the worker detached with the pool arguments" and "passes the credential as --auth-token-file even with a stale credential kind" cover the Cursor token file. The runner and worker start cases set the variable directly in the container's environment, which is how the agent now delivers it.
  • Not covered here: the agent setting the variable from the secret, and the relay refusing a 1.0.0 template in parameter mode. Both are behaviour of coder/coder and coder/agent-relay, not of the modules, and need a running deployment.
  • Commands and results:
    • terraform init -upgrade && terraform test in each module directory: 25 passed, 0 failed, in both.
    • bun test main.test.ts in each module directory with Docker running: 25 passed (Claude Code) and 26 passed (Cursor), 0 failed.
    • bun run fmt leaves the files unchanged. ./.github/scripts/version-bump.sh --ci major origin/main passes with both modules at v1.0.0.
Manual Test

Run on 2026-10-28 against this branch, with dogfood (https://dogfood.cdr.dev, a main build 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-relay main at 550f96e (two commits past the #119 merge) in the workspace bobby/Bobby-Agent-Relay, serving the pool demo-runner-bobby that backs the Bobby-Testing environment at claude.ai/code. The 1.0 module is not published, so the run pushed non-active versions of dogfood's agent-relay-template-docker whose module source points at this branch over git, and pinned the pool to them with the relay's template_version setting. 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, dispatched reading 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.

# Scenario Result
1 Environment: relay from main with #119, dogfood version and date, two template versions pushed non-active and pinned from the pool Pass
2 The relay starts in secrets mode and verifies the 1.0 template, which has no credential parameter Pass
3 A session is dispatched on the pinned 1.0 version; the work order appears nowhere in the log Pass
4 The build has no credential parameter row, not even an empty one Pass
5 The build carries the secret, with no value visible through the API, the CLI or the audit log Pass
6 The secret reaches the start script with no coder_env to carry it; the runner starts Pass
7 dispatched reads true on a relay build Pass
8 The session is served from inside the workspace Pass
9 Nothing in the build knows the credential: no parameter, no coder_env, nothing in the provisioner log Pass
10 A manual restart: the new build has no secret, dispatched stays true, the new idle message prints; then Anthropic redelivers and the relay rebuilds in place Pass
11 The session ends: the reaper deletes the workspace; other owners' workspaces are untouched Pass
12 A relay that still requires the parameter refuses the 1.0 version at startup and builds nothing Pass
13 The 0.2.0 module on the same relay: served, with the warning, the empty parameter row and dispatched false Pass
14 The modules' own tests, and the new guard failing against main Pass
15 The branch head's idle wording, end to end Pass
16 Cleanup Pass

Facts worth knowing from the run:

  1. The three visible differences from 0.2.0 are exactly the three the PR makes. Scenario 13 ran the 0.2.0 module on the same relay, in the same mode, with the same secret, and the only things that differ from scenarios 4, 7 and 9 are: the agent_relay_credential= row (present and empty on 0.2.0, absent on 1.0), agent_relay_dispatched (false on 0.2.0, true on 1.0) and the provisioner log (two lines for the credential parameter and one runner_environment_secret resource on 0.2.0, none on 1.0). The runner served in both.
  2. A manual restart keeps dispatched true, 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 empty agent_relay_attempt, the same agent_relay_delivery_id and so dispatched still true, which is the "created by Agent Relay" meaning settled in review. The reaper left the idle workspace alone, because idle is not a terminal state. Eleven minutes later Anthropic redelivered the session with spawn_reason=runner_lost and attempt=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.
  3. The template versions were one commit behind the branch when the run started. They were built from 976bc64; the branch had ea58fba on top, which rewrites the idle message, and the head aff46ce is a merge of main with 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 from aff46ce and repeated the restart: the head's two lines printed.
  4. The pre-feat(local-windows-rdp): local Windows RDP using coder desktop #119 relay is the stand-in for every released relay. Dogfood only reports -devel versions, 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 last main before the merge, has the same contract as every released relay (always requires agent_relay_credential) and has the template_version pin, so it could be pointed at the non-active 1.0 version. Scenario 12 is that build refusing.
  5. Dogfood rolled three times during the run (0fd07754f5, 9f41c06fcd, 7f6ce9c8ab), and CI replaced the template's active version (250477e to d462b27) at 19:41 UTC. The pinned pool noticed neither, and no step of the run activated a version.
  6. The wsstatus helper from the feat(local-windows-rdp): local Windows RDP using coder desktop #119 run had a bug. Its jq path looked for .key and .value on an agent metadata item, which the API nests as .description.key and .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's wsstatus lines 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 /tmp had been cleared by a rebuild. The clone was moved to main with #119 merged, the config was given the pin, and two template versions were pushed next to the active one: relay75-976bc64 with both modules from this branch over git, and relay75-0x with the active version's 0.2.0 and 0.4.0 modules, both with one coder_metadata item added so the module's dispatched output is visible through the API.

# laptop
coder whoami
coder port-forward bobby/Bobby-Agent-Relay --tcp 2119:2119 --tcp 2118:2118 &

# workspace bobby/Bobby-Agent-Relay
cat /proc/loadavg
cd ~/agent-relay && git fetch origin && git checkout main && git merge --ff-only origin/main
git log -1 --format='%h %s'
grep -c 'from before build secrets' internal/services/serviceclaudecoderunners/claudecoderunners.go
make build && bin/agent-relay version
chmod 600 agent-relay.yaml
sed -i '/^    template: agent-relay-template-docker$/a\    template_version: relay75-976bc64' agent-relay.yaml
sed -n '/claudeCodeRunnerPools/,$p' agent-relay.yaml | sed 's/\(secret: "sk-ant-cc-\).*/\1…"/'
curl -s "$CODER_URL/api/v2/buildinfo" | jq -r '.version, .external_url'

# laptop
gh api repos/coder/coder/commits/0fd07754f509544df6f8e9e1c12e0e64c68557af --jq '.commit.committer.date'
gh api repos/coder/coder/compare/7aef110ca2...0fd07754f509544df6f8e9e1c12e0e64c68557af --jq '.status'

# workspace: the two template versions
cd /tmp/relay75 && coder templates pull agent-relay-template-docker tpl -y
grep -n -A3 'module "claude_code_runner"' tpl/main.tf | grep 'source\|version'
cp -r tpl tpl-1.0 && REF=976bc64194f325a7b68fa6621089f337106c3847
sed -i -e "s|source     = \"registry.coder.com/coder/agent-relay-claude-code/coder\"|source     = \"git::https://github.com/coder/registry.git//registry/coder/modules/agent-relay-claude-code?ref=$REF\"|" \
       -e "s|source     = \"registry.coder.com/coder/agent-relay-cursor/coder\"|source     = \"git::https://github.com/coder/registry.git//registry/coder/modules/agent-relay-cursor?ref=$REF\"|" \
       -e '/^  version    = "0\.[0-9]*\.0"$/d' tpl-1.0/main.tf
# plus, in both tpl-1.0 and tpl-0x, this item appended to the container's coder_metadata block:
#   item { key = "agent_relay_dispatched"
#          value = tostring(try(module.claude_code_runner[0].dispatched, module.cursor_worker[0].dispatched, "no module")) }
coder templates push agent-relay-template-docker -d tpl-1.0 --name relay75-976bc64 --activate=false -y -m "RELAY-75 test: both modules from coder/registry#1215 at 976bc64"
coder templates push agent-relay-template-docker -d tpl-0x --name relay75-0x --activate=false -y -m "RELAY-75 test: the active version's 0.2.0/0.4.0 modules"
coder templates versions list agent-relay-template-docker | grep -i 'relay75\| active'
[  Coder is running at https://dogfood.cdr.dev, You're authenticated as bobby !  ]
Ready!
11.09 10.80 15.92 8/59877 109321
550f96e feat!: normalize pool idle release timeouts (#125)
1
agent-relay v0.12.0-26-g550f96e
claudeCodeRunnerPools:
  - name: demo-runner-bobby
    secret: "sk-ant-cc-…"
    organization: coder
    template: agent-relay-template-docker
    template_version: relay75-976bc64
v2.38.3-devel+0fd07754f5
https://github.com/coder/coder/commit/0fd07754f509544df6f8e9e1c12e0e64c68557af
2026-10-28T18:16:55Z
ahead
199-  source     = "registry.coder.com/coder/agent-relay-claude-code/coder"
200-  version    = "0.2.0"
2026-10-28 19:09:01.785Z Downloading git::https://github.com/coder/registry.git?ref=976bc64194f325a7b68fa6621089f337106c3847 for claude_code_runner...
2026-10-28 19:09:14.192Z module.claude_code_runner[0].coder_env.agent_relay_claude_code_lock_to_account: Plan to create
2026-10-28 19:09:14.192Z module.claude_code_runner[0].coder_env.agent_relay_client_platform: Plan to create
2026-10-28 19:09:14.192Z module.claude_code_runner[0].coder_env.agent_relay_claude_code_release_idle_session_min: Plan to create
Updated version at Oct 28 19:09:16!
2026-10-28 19:09:20.672Z module.claude_code_runner[0].coder_env.runner_environment_secret: Plan to create
Updated version at Oct 28 19:09:23!
250477e                                   2026-10-07T12:28:29Z  dogfood-template-admin  Succeeded  Active
relay75-0x                                2026-10-28T19:09:17Z  bobby                   Succeeded
relay75-976bc64                           2026-10-28T19:09:01Z  bobby                   Succeeded

The push's own plan is an early look at the change, before any workspace exists: the 1.0 version plans three coder_env resources in the module and no runner_environment_secret; the 0.x version plans runner_environment_secret. The api, buildparams, buildsecrets, wsmeta, wsstatus, buildlogs and agentlog helpers used below are thin curl and jq wrappers around the dogfood API using the workspace's own session token.

2. The relay starts in secrets mode and verifies the 1.0 template
# workspace
cd ~/agent-relay
CODER_AGENT_RELAY_CODERD_SESSION_TOKEN="$CODER_SESSION_TOKEN" setsid nohup bin/agent-relay start --config agent-relay.yaml > /tmp/relay75/relay.log 2>&1 &
grep 'prerelease version\|authenticated against coderd\|runner pool configured\|BYOC contract\|pool serving\|poll loop started' /tmp/relay75/relay.log
grep -c 'from before build secrets' /tmp/relay75/relay.log || true
grep -ci 'error\|missing coderd' /tmp/relay75/relay.log
grep -c 'sk-ant-cc-' /tmp/relay75/relay.log
2026-10-28 19:12:08.450 [warn]  coderd: coderd reported prerelease version "v2.38.3-devel+0fd07754f5"; the v2.37.0 minimum cannot be enforced against -devel builds
2026-10-28 19:12:08.477 [info]  coderd: authenticated against coderd  url=https://dogfood.cdr.dev/  coderd_version=v2.38.3-devel+0fd07754f5  user=bobby  build_secrets=true
2026-10-28 19:12:08.570 [info]  claude-code-runners: runner pool configured  provider=claude_code  pool=demo-runner-bobby  organization=coder  organization_id=703f72a1-76f6-4f89-9de6-8a3989693fe5  template=agent-relay-template-docker  template_version=relay75-976bc64  claude_tag_channel_user=""
2026-10-28 19:12:08.760 [info]  claude-code-runners: template verified, pool serving  provider=claude_code  pool=demo-runner-bobby  template=agent-relay-template-docker
2026-10-28 19:12:08.760 [info]  claude-code-runners: poll loop started  provider=claude_code  pool=demo-runner-bobby  interval=5s  expected_spawn_seconds=300
0
0
0

"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 at http://localhost:2119 loaded without a login, header agent-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

1b-status-page

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 of env | grep -c SELF_HOSTED_RUNNER and of coder version".

# workspace
grep 'resolved session owner\|workspace build created\|runner handoff' /tmp/relay75/relay.log | cut -c1-120
wsname
build "$(wsname)" | jq -r '.template_version_name + " build " + (.build_number|tostring) + " " + .status'
grep -c 'sk-ant-cc-' /tmp/relay75/relay.log
2026-10-28 19:32:24.069 [info]  claude-code-runners: resolved session owner  provider=claude_code  pool=demo-runner-bobby  …  owner=bobby
2026-10-28 19:32:24.801 [info]  claude-code-runners: workspace build created  …  workspace_name=cc-038ddde2-227f-428b-b8c3-c7131  owner=bobby
2026-10-28 19:32:48.861 [info]  claude-code-runners: workspace ready, runner handoff  …  workspace_name=cc-038ddde2-227f-428b-b8c3-c7131  owner=bobby
cc-038ddde2-227f-428b-b8c3-c7131
relay75-976bc64 build 1 running
0

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
# workspace
WS=$(wsname)
buildparams "$WS"
buildparams "$WS" | wc -l
buildparams "$WS" | grep -c 'agent_relay_credential' || true
Region=us-pittsburg…(13 chars)
agent_relay_attempt=0
agent_relay_claude_code_lock_to_account=user_011STQU…(29 chars)
agent_relay_claude_code_release_idle_session_min=60
agent_relay_client_platform=web_claude_a…(13 chars)
agent_relay_delivery_id=77e16ce8-e40…(36 chars)
agent_relay_pool=cc:demo-runn…(20 chars)
agent_relay_session_id=038ddde2-227…(36 chars)
container_image=ghcr.io/code…(42 chars)
cpu=2
forced_failure_mode=none
memory=4
self_hosted_provider=claude
13
0

Thirteen rows, the template's six and the module's seven, and no agent_relay_credential row. 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
# workspace
buildsecrets "$WS"
coder secret list --workspace "bobby/$WS" -c name,source,env,ephemeral,created
buildsecrets "$WS" | grep -c '"value"' || true
auditsecrets "$WS" 1 | jq -c '{action, workspace_secrets:.additional_fields.workspace_secrets, workspace_secrets_error:.additional_fields.workspace_secrets_error}'
[
  {
    "id": "f34b14c0-5f57-49c9-925f-d4327be6348b",
    "name": "agent_relay_credential",
    "description": "",
    "env_name": "SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET",
    "file_path": "",
    "enabled": true,
    "created_at": "2026-10-28T19:32:24.225721Z",
    "updated_at": "2026-10-28T19:32:24.225721Z",
    "workspace_build_id": "2544c264-1912-4387-9d0c-0ea14269ec5a",
    "ephemeral": true
  }
]
NAME                    SOURCE  ENV                                    EPHEMERAL  CREATED
agent_relay_credential  build   SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET  true       19 minutes ago
0
{"action":"start","workspace_secrets":[{"name":"agent_relay_credential","source":"request","env_name":"SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET","ephemeral":true,"file_path":""}],"workspace_secrets_error":null}

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
# workspace
coder ssh "bobby/$WS" -- bash -s <<'EOS'
echo "hostname: $(hostname)"
test -n "$SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET" && echo "variable set (${#SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET} chars)" || echo "variable EMPTY"
echo "prefix: ${SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET:0:10}"
pgrep -af "self-hosted-runner" | grep -v pgrep | cut -c1-120
cat ~/.coder-modules/coder/agent-relay-claude-code/runner-state
env | grep -o '^SELF_HOSTED_RUNNER_LOCK_TO_ACCOUNT=\|^AGENT_RELAY_CLIENT_PLATFORM=.*\|^AGENT_RELAY_CLAUDE_CODE_RELEASE_IDLE_SESSION_MIN=.*' | sort
EOS
wsstatus "$WS"
hostname: cc-038ddde2-227f-428b-b8c3-c7131
variable set (1260 chars)
prefix: sk-ant-cc-
88 claude self-hosted-runner --capacity 1 --base-dir /home/coder/workspace --exit-if-unused-min 10 --release-idle-sessio
working 88
AGENT_RELAY_CLAUDE_CODE_RELEASE_IDLE_SESSION_MIN=60
AGENT_RELAY_CLIENT_PLATFORM=web_claude_ai
SELF_HOSTED_RUNNER_LOCK_TO_ACCOUNT=
serving

On 0.2.0 the same variable arrived through the agent overriding the module's empty coder_env. On 1.0 there is no coder_env of 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
# workspace
wsmeta "$WS"
buildparams "$WS" | grep agent_relay_delivery_id
agent_relay_dispatched=true
agent_relay_delivery_id=77e16ce8-e40…(36 chars)

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 reads false (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

2e-dispatched-true

8. The session is served
# workspace
wsstatus "$(wsname)"
curl -s http://127.0.0.1:2119/api/status | jq -c '.sections[0].payload | {header_note, stats}'
serving
{"header_note":"polling · 1 pools","stats":[{"label":"in flight","value":"1","detail":"0 building · 1 serving"},{"label":"claimed","value":"1","detail":"work orders since start"},{"label":"nacks","value":"0","detail":"0 retryable · 0 terminal"}]}

The runner answered inside the dispatched workspace: hostname cc-038ddde2-227f-428b-b8c3-c7131, three SELF_HOSTED_RUNNER lines in the session's environment, coder version v2.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 no coder_env on the way.

Screen capture: the session's answer, the container's hostname and the coder version

2f-session-answer

Screen capture: the status page with the session in the serving column and the pool live 1

2f-status-page-serving

9. Nothing in the build knows the credential
# workspace
buildlogs "$WS" > /tmp/relay75/build.log
wc -l < /tmp/relay75/build.log
grep -c 'sk-ant-cc-' /tmp/relay75/build.log || true
grep -c 'agent_relay_credential' /tmp/relay75/build.log || true
grep -c 'runner_environment_secret' /tmp/relay75/build.log || true
grep 'module.claude_code_runner\[0\].data.coder_parameter' /tmp/relay75/build.log | sed 's/: .*//' | sort -u
grep 'module.claude_code_runner\[0\].coder_env' /tmp/relay75/build.log | sed 's/: .*//' | sort -u
108
0
0
0
module.claude_code_runner[0].data.coder_parameter.agent_relay_attempt
module.claude_code_runner[0].data.coder_parameter.agent_relay_claude_code_lock_to_account
module.claude_code_runner[0].data.coder_parameter.agent_relay_claude_code_release_idle_session_min
module.claude_code_runner[0].data.coder_parameter.agent_relay_client_platform
module.claude_code_runner[0].data.coder_parameter.agent_relay_delivery_id
module.claude_code_runner[0].data.coder_parameter.agent_relay_pool
module.claude_code_runner[0].data.coder_parameter.agent_relay_session_id
module.claude_code_runner[0].coder_env.agent_relay_claude_code_lock_to_account
module.claude_code_runner[0].coder_env.agent_relay_claude_code_release_idle_session_min
module.claude_code_runner[0].coder_env.agent_relay_client_platform

The #119 run on 0.2.0 had two lines for agent_relay_credential here and one more coder_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
# workspace
coder restart "bobby/$WS" -y; sleep 30
build "$WS" | jq -c '{build_number, transition, status:.job.status, template_version_name}'
buildsecrets "$WS" 1 | jq -c '.[] | {name, ephemeral, cleared_at}'
buildsecrets "$WS"
buildparams "$WS" | grep -c 'agent_relay_credential' || true
buildparams "$WS" | grep 'agent_relay_delivery_id\|agent_relay_attempt'
wsmeta "$WS"
sleep 20; wsstatus "$WS"
agentlog "$WS" | grep -A1 'No work order'
{"build_number":3,"transition":"start","status":"succeeded","template_version_name":"relay75-976bc64"}
{"name":"agent_relay_credential","ephemeral":true,"cleared_at":"2026-10-28T20:29:16.143477Z"}
[]
0
agent_relay_attempt=
agent_relay_delivery_id=77e16ce8-e40…(36 chars)
agent_relay_dispatched=true
idle
No work order set. This workspace was created manually, not by Agent Relay.
Idling. Delete this workspace manually; only Agent Relay can deliver a work order.

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_id is persistent and carries forward, so dispatched stays true; agent_relay_attempt is ephemeral and resets. The start script took the no-credential branch and printed the idle message of 976bc64, 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

3a-agent-log-idle

Eleven minutes later, with the session page still open at claude.ai, Anthropic redelivered the session because its runner had gone:

# workspace
awk '$2 >= "20:33:00"' /tmp/relay75/relay.log | grep -v '\[debu\]' | cut -c1-120
build "$WS" | jq -c '{build_number, transition, status:.job.status, template_version_name, reason}'
buildsecrets "$WS" | jq -c '.[] | {name, env_name, ephemeral, created_at, workspace_build_id, cleared_at}'
buildparams "$WS" | grep 'agent_relay_delivery_id\|agent_relay_attempt'
wsmeta "$WS"; wsstatus "$WS"
grep -c 'delete' /tmp/relay75/relay.log || true
2026-10-28 20:40:04.055 [info]  claude-code-runners: resolved session owner  …  delivery_id=ef48cfda-4403-5777-b122-bbdd2b53b39a  attempt=1  spawn_reason=runner_lost  …
2026-10-28 20:40:04.204 [info]  claude-code-runners: existing workspace is not serving the session, rebuilding  …
2026-10-28 20:40:04.526 [info]  claude-code-runners: workspace rebuild created  …
2026-10-28 20:40:13.843 [info]  claude-code-runners: workspace ready, runner handoff  …
{"build_number":4,"transition":"start","status":"succeeded","template_version_name":"relay75-976bc64","reason":"initiator"}
{"name":"agent_relay_credential","env_name":"SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET","ephemeral":true,"created_at":"2026-10-28T20:40:04.317123Z","workspace_build_id":"05d5b8ae-6cdc-4134-8197-060c14043a27","cleared_at":null}
agent_relay_attempt=1
agent_relay_delivery_id=ef48cfda-440…(36 chars)
agent_relay_dispatched=true
serving
0

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.

# workspace
grep 'reaper' /tmp/relay75/relay.log | cut -c1-200
grep -c nack /tmp/relay75/relay.log || true
sleep 60
api "/workspaces?q=owner:bobby+template:agent-relay-template-docker" | jq '.count'
grep -o 'workspace_name=[^ ]*' /tmp/relay75/relay.log | sort -u
grep -c 'stephenmcghie\|steven/\|jared' /tmp/relay75/relay.log || true
2026-10-28 20:44:28.729 [info]  reaper: deleting workspace  workspace_id=1e425243-54f8-4c4f-a28a-59fe6fde8e32  workspace_name=cc-038ddde2-227f-428b-b8c3-c7131  build_status=running  status="done 0"  age=4m24s  reason="the session finished"
2026-10-28 20:44:28.889 [info]  reaper: workspace delete queued  workspace_id=1e425243-54f8-4c4f-a28a-59fe6fde8e32  workspace_name=cc-038ddde2-227f-428b-b8c3-c7131  build_status=running  status="done 0"  age=4m24s
0
0
workspace_name=cc-038ddde2-227f-428b-b8c3-c7131
0

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

3b-session-released

12. A relay that still requires the parameter refuses the 1.0 version

Built from 4f9a391, the last agent-relay main before #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.

# workspace
cd ~/agent-relay && git worktree add /tmp/relay75/pre119 4f9a391a8f
(cd /tmp/relay75/pre119 && CGO_ENABLED=0 go build -ldflags "-X github.com/coder/agent-relay/internal/version.Version=$(git describe --tags --always)" -o ~/agent-relay/bin/agent-relay-pre119 ./cmd/agent-relay)
bin/agent-relay-pre119 version
grep -c 'WithCredential' /tmp/relay75/pre119/internal/services/serviceclaudecoderunners/claudecoderunners.go || echo "no WithCredential: the contract always requires the parameter"
cat > /tmp/relay75/relay-refusal.yaml <<'YAML'
coderdURL: https://dogfood.cdr.dev/
logLevel: debug
statusUIEnable: true
statusUIAddress: 127.0.0.1:3119
statusUIAuth: insecure
claudeCodeRunnerPools:
  - name: relay75-refusal
    secret: "sk-ant-cc-placeholder-never-sent"
    organization: coder
    template: agent-relay-template-docker
    template_version: relay75-976bc64
YAML
CODER_AGENT_RELAY_CODERD_SESSION_TOKEN="$CODER_SESSION_TOKEN" timeout -s INT -k 10 75 bin/agent-relay-pre119 start --config /tmp/relay75/relay-refusal.yaml > /tmp/relay75/relay-refusal.log 2>&1
curl -s http://127.0.0.1:3119/api/status | jq -c '.sections[0].payload | {header_note, pools:[.tables[]?.rows[]? | map(.text // .)]}'   # 40 s in
grep 'authenticated against coderd\|runner pool configured\|BYOC contract\|pool serving\|poll loop' /tmp/relay75/relay-refusal.log | cut -c1-400
grep -c 'template failed the BYOC contract check' /tmp/relay75/relay-refusal.log
grep -c 'poll loop started' /tmp/relay75/relay-refusal.log || true
grep -ci 'anthropic\|placeholder' /tmp/relay75/relay-refusal.log || true
agent-relay v0.12.0-19-g4f9a391
0
no WithCredential: the contract always requires the parameter
{"header_note":"polling · 1 pools · 1 degraded","pools":[["relay75-refusal","coder","agent-relay-template-docker","degraded · template \"agent-relay-template-docker\" is not BYOC-compatible: missing parameter \"agent_relay_credential\" (include the registry.coder.com/coder/agent-relay-claude-code/coder module)","0"]]}
2026-10-28 20:49:34.125 [info]  coderd: authenticated against coderd  url=https://dogfood.cdr.dev/  coderd_version=v2.38.3-devel+7f6ce9c8ab  user=bobby
2026-10-28 20:49:34.236 [info]  claude-code-runners: runner pool configured  provider=claude_code  pool=relay75-refusal  organization=coder  organization_id=703f72a1-76f6-4f89-9de6-8a3989693fe5  template=agent-relay-template-docker  template_version=relay75-976bc64  claude_tag_channel_user=""
2026-10-28 20:49:34.606 [warn]  claude-code-runners: template failed the BYOC contract check, retrying  provider=claude_code  pool=relay75-refusal  template=agent-relay-template-docker  retry_interval=30s  error="template \"agent-relay-template-docker\" is not BYOC-compatible: missing parameter \"agent_relay_credential\" (include the registry.coder.com/coder/agent-relay-claude-code/coder module)"
2026-10-28 20:50:04.856 [warn]  claude-code-runners: template failed the BYOC contract check, retrying  … (same)
2026-10-28 20:50:35.126 [warn]  claude-code-runners: template failed the BYOC contract check, retrying  … (same)
3
0
0

The authenticate line has no build_secrets field, 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
# workspace
pkill -INT -x agent-relay; sleep 5
sed -i 's/template_version: relay75-976bc64/template_version: relay75-0x/' agent-relay.yaml
CODER_AGENT_RELAY_CODERD_SESSION_TOKEN="$CODER_SESSION_TOKEN" setsid nohup bin/agent-relay start --config agent-relay.yaml >> /tmp/relay75/relay.log 2>&1 &
grep 'runner pool configured\|from before build secrets\|pool serving' /tmp/relay75/relay.log | tail -3
grep -c 'from before build secrets' /tmp/relay75/relay.log
# then a new session at claude.ai/code in Bobby-Testing, with the same prompt
WS=$(wsname); echo "$WS"
build "$WS" | jq -r '.template_version_name + " build " + (.build_number|tostring) + " " + .status'
buildparams "$WS" | grep 'agent_relay_credential\|agent_relay_delivery_id'
buildsecrets "$WS" | jq -c '.[] | {name, env_name, ephemeral}'
wsmeta "$WS"
wsstatus "$WS"
buildlogs "$WS" | grep 'agent_relay_credential\|runner_environment_secret' | sed 's/: .*//' | sort | uniq -c
2026-10-28 20:53:13.259 [info]  claude-code-runners: runner pool configured  …  template=agent-relay-template-docker  template_version=relay75-0x  claude_tag_channel_user=""
2026-10-28 20:53:13.521 [warn]  claude-code-runners: template serves but is on a registry.coder.com/coder/agent-relay-claude-code/coder module from before build secrets, upgrade it to 1.0 or newer  pool=demo-runner-bobby  template=agent-relay-template-docker  warning="parameter \"agent_relay_credential\" is declared but no longer stamped: this coderd accepts build secrets, so the credential arrives as a workspace secret and the template can move to a module version without the parameter"
2026-10-28 20:53:13.521 [info]  claude-code-runners: template verified, pool serving  provider=claude_code  pool=demo-runner-bobby  template=agent-relay-template-docker
1
cc-038ddde2-0693-4314-90b5-e8610
relay75-0x build 1 running
agent_relay_credential=
agent_relay_delivery_id=8edc6155-3e4…(36 chars)
{"name":"agent_relay_credential","env_name":"SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET","ephemeral":true}
agent_relay_dispatched=false
serving
      1 module.claude_code_runner[0].coder_env.runner_environment_secret
      2 module.claude_code_runner[0].data.coder_parameter.agent_relay_credential

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:

1.0 (relay75-976bc64) 0.2.0 (relay75-0x)
agent_relay_credential parameter row absent present, empty
agent_relay_dispatched true false
provisioner log: agent_relay_credential 0 lines 2 lines
provisioner log: runner_environment_secret 0 1 resource
secret on the build one, the same one, the same
runner serving serving
relay startup "template verified, pool serving" the warning, then the same line

That 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
# laptop, ~/work/registry on this branch at ea58fba, Docker running
for m in agent-relay-claude-code agent-relay-cursor; do (cd registry/coder/modules/$m && terraform init -upgrade >/dev/null && terraform test 2>&1 | tail -1); done
for m in agent-relay-claude-code agent-relay-cursor; do (cd registry/coder/modules/$m && bun test main.test.ts 2>&1 | grep -E '^ *[0-9]+ (pass|fail)|Ran'); done
# the guard against main's main.tf
git checkout -q main -- registry/coder/modules/agent-relay-claude-code/main.tf
(cd registry/coder/modules/agent-relay-claude-code && bun test main.test.ts -t "sets no coder_env" 2>&1 | grep -E '^ *[0-9]+ (pass|fail)|^\(fail\)|Expected|Received')
git checkout -q HEAD -- registry/coder/modules/agent-relay-claude-code/main.tf; git status --porcelain
Success! 25 passed, 0 failed.
Success! 25 passed, 0 failed.
 25 pass
 0 fail
Ran 25 tests across 1 files. [42.80s]
 26 pass
 0 fail
Ran 26 tests across 1 files. [23.35s]
error: expect(received).not.toContain(expected)
Expected to not contain: "SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET"
Received: [ "SELF_HOSTED_RUNNER_LOCK_TO_ACCOUNT", "AGENT_RELAY_CLAUDE_CODE_RELEASE_IDLE_SESSION_MIN",
(fail) agent-relay-claude-code > sets no coder_env for the work order's variable [211.27ms]
 0 pass
 1 fail

With main's main.tf in 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.

# workspace
REF=$(git ls-remote https://github.com/coder/registry.git refs/pull/1215/head | cut -f1)
cp -r tpl-1.0 tpl-head && sed -i "s|ref=976bc64194f325a7b68fa6621089f337106c3847|ref=$REF|g" tpl-head/main.tf
coder templates push agent-relay-template-docker -d tpl-head --name relay75-aff46ce --activate=false -y -m "RELAY-75 test: both modules from coder/registry#1215 head aff46ce"
pkill -INT -x agent-relay; sleep 5
sed -i 's/template_version: relay75-0x/template_version: relay75-aff46ce/' agent-relay.yaml
CODER_AGENT_RELAY_CODERD_SESSION_TOKEN="$CODER_SESSION_TOKEN" setsid nohup bin/agent-relay start --config agent-relay.yaml >> /tmp/relay75/relay.log 2>&1 &
grep 'runner pool configured\|pool serving' /tmp/relay75/relay.log | tail -2 | cut -c1-100
# then a new session at claude.ai/code in Bobby-Testing
WS=$(wsname); build "$WS" | jq -r '.template_version_name + " build " + (.build_number|tostring) + " " + .status'
buildparams "$WS" | grep -c agent_relay_credential || true
wsmeta "$WS"; wsstatus "$WS"
coder restart "bobby/$WS" -y; sleep 30
build "$WS" | jq -c '{build_number, transition, status:.job.status, template_version_name}'
buildsecrets "$WS"
wsmeta "$WS"; sleep 20; wsstatus "$WS"
agentlog "$WS" | grep -A1 'No work order'
Updated version at Oct 28 21:10:48!
2026-10-28 21:10:54.456 [info]  claude-code-runners: runner pool configured  …  template_version=relay75-aff46ce
2026-10-28 21:10:54.619 [info]  claude-code-runners: template verified, pool serving
relay75-aff46ce build 1 running
0
agent_relay_dispatched=true
serving
{"build_number":3,"transition":"start","status":"succeeded","template_version_name":"relay75-aff46ce"}
[]
agent_relay_dispatched=true
idle
No work order in this build. Agent Relay delivers one only on the build it dispatches.
Idling. If Agent Relay did not create this workspace, delete it.

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
# workspace
pkill -INT -x agent-relay; sleep 5; pgrep -x agent-relay -a || echo "no relay running"
pgrep -f 'agent-relay-pre119 start' -a || echo "no pre-#119 relay running"
coder list --search 'owner:bobby template:agent-relay-template-docker' | grep -c '^ *bobby/cc-' || echo "no bobby/cc-* workspace"
cd ~/agent-relay && git worktree remove /tmp/relay75/pre119 --force; git worktree prune; git log -1 --format='%h %s'; git status --porcelain
sed -i '/^    template_version: relay75-/d' agent-relay.yaml
grep -c 'template_version' agent-relay.yaml || echo "pin removed"
grep -c 'sk-ant-cc-' agent-relay.yaml; ls -l agent-relay.yaml
coder templates versions list agent-relay-template-docker | grep -i 'relay75\| active'
grep -o 'workspace_name=[^ ]*' /tmp/relay75/relay.log | sort | uniq -c
grep -c 'sk-ant-cc-' /tmp/relay75/relay.log /tmp/relay75/relay-refusal.log

# laptop
pkill -f 'coder port-forward bobby/Bobby-Agent-Relay'
no relay running
no pre-#119 relay running
no bobby/cc-* workspace
550f96e feat!: normalize pool idle release timeouts (#125)
pin removed
1
-rw------- 1 coder coder 1164 Oct 28 21:15 agent-relay.yaml
d462b27                                   2026-10-28T19:41:39Z  dogfood-template-admin  Succeeded  Active
relay75-0x                                2026-10-28T19:09:17Z  bobby                   Succeeded
relay75-976bc64                           2026-10-28T19:09:01Z  bobby                   Succeeded
relay75-aff46ce                           2026-10-28T21:10:39Z  bobby                   Succeeded
      4 workspace_name=cc-038ddde2-0693-4314-90b5-e8610
      2 workspace_name=cc-038ddde2-0d27-496d-89e4-04883
      7 workspace_name=cc-038ddde2-227f-428b-b8c3-c7131
/tmp/relay75/relay.log:0
/tmp/relay75/relay-refusal.log:0

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 three relay75-* 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, all bobby/cc-*.

…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.
@BobbyHo BobbyHo added the version:major Add to PRs requiring a major version upgrade label Oct 28, 2026
@linear-code

linear-code Bot commented Oct 28, 2026

Copy link
Copy Markdown

RELAY-75

@BobbyHo

BobbyHo commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review Please limit the review to P0, P1, and P2 issues only.

@coder-agents-review

coder-agents-review Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Chat: Review posted | View chat
Requested: 2026-10-28 18:36 UTC by @BobbyHo

Review history
  • R1 (2026-10-28): 12 reviewers, 2 P2, COMMENT. Review
  • R2 (2026-10-28): 10 reviewers, 4 P2, COMMENT. Review
  • R3 (2026-10-28): 4 reviewers, 4 P2, APPROVE. Review

deep-review v0.13.0 | Round 3 | af57511..aff46ce

Last posted: Round 3, 4 findings (4 P2), APPROVE. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Author fixed (976bc64) agent-relay-claude-code/main.tf:361 dispatched reads non-ephemeral agent_relay_delivery_id, so it is true on restart builds with no work order R1 Mafuuu P2, Mafu-san P2, Melody P3, Pariston P3, Hisoka P3, Razor P3, Bisky P3, Leorio P3, Gon P3, Kurapika Note, Zoro Note Yes
CRF-2 P2 Author fixed (976bc64) agent-relay-cursor/main.tf:341 Same dispatched carry-forward in Cursor; no ephemeral parameter remains R1 Mafuuu P2, Mafu-san P2, Melody P3, Hisoka P3, Razor P3, Gon P3, Kurapika Note, Zoro Note Yes
CRF-3 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/main.test.ts:203 Guard test checks coder_env name, passes if agent_relay_credential parameter returns R1 Bisky No
CRF-4 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-cursor/main.test.ts:186 Same guard-test gap in Cursor R1 Bisky No
CRF-5 P3 Author fixed (976bc64) agent-relay-claude-code/main.tftest.hcl:143 dispatched == true case untested in both modules R1 Bisky No
CRF-6 P3 Dropped by orchestrator (re-raised R2 at P3 by Mafuuu, Mafu-san, Leorio, Razor P4; below the author's P0-P2 limit) agent-relay-claude-code/main.tf:169 agent_relay_delivery_id description still says "Tracing only" R1 Bisky No
CRF-7 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/main.test.ts:192 Comment says an agent restart loses the secret; only a new build does R1 Gon No
CRF-8 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-cursor/main.test.ts:175 Same agent-restart comment error in Cursor R1 Gon No
CRF-9 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/start.sh.tftpl:36 Five names for one delivered value across comments R1 Gon No
CRF-10 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-cursor/start.sh.tftpl:4 Says Agent Relay created the session token; Cursor mints it R1 Gon No
CRF-11 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-claude-code/main.tf:27 Header comment bloat R1 Gon P2 No
CRF-12 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-cursor/main.tf:23 Header comment bloat R1 Gon P2 No
CRF-13 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-cursor/main.tf:235 coder_env comment describes the token, not the resource R1 Gon P2 No
CRF-14 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-claude-code/main.tf:260 coder_env comment repeats header R1 Gon P2 No
CRF-15 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-claude-code/main.tf:22 "reset between builds" restates "ephemeral" R1 Gon P2 No
CRF-16 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-claude-code/start.sh.tftpl:3 Start script header repeats delivery path R1 Gon P2 No
CRF-17 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-claude-code/start.sh.tftpl:36 Idle comment restates the if below it R1 Gon P2 No
CRF-18 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-cursor/start.sh.tftpl:51 Same idle comment restatement R1 Gon P2 No
CRF-19 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-claude-code/main.test.ts:204 Test comment repeats header R1 Gon P2 No
CRF-20 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-cursor/main.test.ts:187 Test comment repeats header R1 Gon P2 No
CRF-21 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-cursor/main.tftest.hcl:43 "None are ephemeral" restates list, gives wrong reason R1 Gon P2 No
CRF-22 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-cursor/main.tftest.hcl:77 Comment describes token, no assert checks it R1 Gon P2 No
CRF-23 P4 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-cursor/README.md:96 "the agent" means Coder agent and Cursor agent in one paragraph R1 Razor No
CRF-24 OOS Out of scope (body) coder/coder agent/agent.go:1598 Secret env reaches every agent process; file_path delivery needs a relay change R1 Kurapika No
CRF-25 OOS Out of scope (body) test/test.ts:236 runTerraformApply leaks CODER_WORKSPACE_* so tests fail inside a workspace R1 Pariston, Bisky, Gon No
CRF-26 OOS Out of scope (body); moved in scope R2 as CRF-30, CRF-31 agent-relay-claude-code/start.sh.tftpl:42 "created manually" printed on restart of a relay workspace R1 Leorio, Gon No
CRF-27 OOS Out of scope (body) agent-relay-claude-code/main.test.ts:182 Test names still say "credential" R1 Gon No
CRF-28 OOS Out of scope (body) tsconfig.json:6 moduleResolution node deprecated in TypeScript 6 R1 Ging-TS No
CRF-29 Note Note (body) agent-relay-claude-code/README.md:54 Coder 2.39.0 and Agent Relay v0.14.0 are not tagged yet R1 Pariston, Mafu-san, Leorio No
CRF-30 P2 Author fixed (ea58fba) agent-relay-claude-code/start.sh.tftpl:42 Restart of a relay-created workspace logs "created manually, not by Agent Relay" and says to delete it, contradicting the new dispatched meaning R2 Mafu-san P2, Leorio P3 Yes
CRF-31 P2 Author fixed (ea58fba) agent-relay-cursor/start.sh.tftpl:57 Same idle message contradiction in Cursor R2 Mafu-san P2, Leorio P3 Yes
CRF-32 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/main.tf:360 Duplicate of a relay workspace copies delivery id, so dispatched is true on a workspace a person created R2 Razor No
CRF-33 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/main.tf:359 Output name dispatched now means "created by Agent Relay"; rename suggested R2 Gon No
CRF-34 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but wordy text; below limit) agent-relay-claude-code/main.tf:360 Output description names start-script internals R2 Gon P2 No
CRF-35 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but wordy text; below limit) agent-relay-cursor/main.tf:340 Same output description wording in Cursor R2 Gon P2 No
CRF-36 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-claude-code/main.tftest.hcl:151 New run comment repeats output description R2 Gon P2 No
CRF-37 P3 Dropped by orchestrator (downgraded P2 to P3, accurate but duplicated comment; below limit) agent-relay-cursor/main.tftest.hcl:173 Same repeated run comment in Cursor R2 Gon P2 No
CRF-38 P4 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/main.tftest.hcl:144 False-case error message still describes the build, not the workspace R2 Gon No
CRF-39 P4 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-cursor/main.tftest.hcl:156 Same error message in Cursor R2 Gon No
CRF-40 OOS Out of scope (body) agent-relay-claude-code/main.tf:166 Duplicate copies session_id and pool, matching relay dedupe filters R2 Razor No
CRF-41 OOS Out of scope (body) agent-relay-cursor/main.tf:166 Display name "Agent Relay delivery" labels the worker identity R2 Gon No
CRF-42 OOS Out of scope (body) agent-relay-claude-code/README.md:179 stopped row names "created manually" for a restarted relay workspace too R2 Leorio No
CRF-43 Note Note (body) agent-relay-claude-code/main.tf:360 README does not tell upgraders that dispatched now stays true on restarts R2 Mafuuu No
CRF-44 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/status.sh.tftpl:7 Status vocabulary comment still defines idle as "created manually" R3 Netero No
CRF-45 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-cursor/status.sh.tftpl:7 Same stale idle definition in Cursor R3 Netero No
CRF-46 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/README.md:135 Rewritten idle row says a restart produces idle; a restart keeps the earlier state R3 Mafu-san, Leorio No
CRF-47 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-cursor/README.md:165 Same idle row error in Cursor R3 Mafu-san, Leorio No
CRF-48 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/start.sh.tftpl:43 Idle message asks the person to decide who created the workspace; module knows via delivery id R3 Chopper No
CRF-49 P3 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-cursor/start.sh.tftpl:58 Same ambiguous idle message in Cursor R3 Chopper No
CRF-50 P4 Dropped by orchestrator (below the author's P0-P2 limit) agent-relay-claude-code/start.sh.tftpl:43 "Idling." printed even when a terminal state is kept (both modules) R3 Chopper No

Contested and acknowledged

None.

Round log

Round 1

Full 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 coderd/dynamicparameters/resolver.go:44-96 by the orchestrator.

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 2

Fix 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 3

Fix round (ea58fba changes two echo lines per start script, the README idle rows and two test assertions; aff46ce merges main, whose goose changes are not in the PR diff). Churn guard: PROCEED, CRF-30 and CRF-31 addressed. Netero: CRF-44, CRF-45 (P3, stale idle comment in status.sh.tftpl). Panel: Mafu-san, Leorio (raised CRF-30/31), Bisky (test assertions changed), wildcard Chopper (random draw). CRF-30, CRF-31 verified fixed by all four. New findings all P3 or lower; convergence of CRF-44/45 with CRF-46/47 (README row wrong plus stale status comment) stays documentation-only, P3. No P0-P2, so no inline findings; replies confirm the two fixes.

Reviewed against 976bc64..aff46ce.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Module Scorecard Check

coder/agent-relay-claude-code: 58 → 67

✅ Score improvement: 58 → 67 (+9).

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.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_path delivery would avoid that but needs a change to the relay's request.
  • test/test.ts:236: runTerraformApply clears only some CODER_* variables, so terraform test and bun test fail with CODER_WORKSPACE_TEMPLATE_ID is required when run inside a Coder workspace.
  • registry/coder/modules/agent-relay-claude-code/start.sh.tftpl:42 and agent-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:182 and 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:54 in 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.
@BobbyHo

BobbyHo commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review Please limit the review to P0, P1, and P2 issues only.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-ephemeral agent_relay_session_id and agent_relay_pool (same in Cursor), so a duplicated workspace matches the relay's param: 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: the stopped row 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 dispatched changed 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.

@BobbyHo BobbyHo self-assigned this Oct 28, 2026
BobbyHo and others added 2 commits October 28, 2026 11:28
…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.
@BobbyHo

BobbyHo commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

/coder-agents-review Please limit the review to P0, P1, and P2 issues only.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@BobbyHo
BobbyHo marked this pull request as ready for review October 28, 2026 21:31
@BobbyHo
BobbyHo requested a review from Emyrk October 28, 2026 21:32
…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
@BobbyHo
BobbyHo merged commit 70d8ca8 into main Oct 29, 2026
5 checks passed
@BobbyHo
BobbyHo deleted the bobby/relay-75-receive-agent-relay-credentials-as-secrets-in-registry branch October 29, 2026 19:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

version:major Add to PRs requiring a major version upgrade

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants