Skip to content

Commit ded9a4a

Browse files
committed
feat(agent-relay-claude-code): support Anthropic-managed git
Add use_anthropic_git_proxy, which authenticates clones server-side so the workspace holds no git credentials. A session a person created uses their GitHub OAuth token; one a bot or agent created uses the organization's GitHub App installation token. That second path is the only git auth that works when the workspace owner is a service account: it has login_type "none", can never sign in, and so can never complete the OAuth flow a coder_external_auth block depends on. Agent Relay creates each workspace as the resolved session owner, so this is reachable today rather than hypothetical. The module takes no position on when to enable it. The flag serves human and bot sessions alike, so a template can gate it on the owner's login_type or turn it on everywhere; both are documented. Pair it with configure_git, defaulting to follow it. The proxy replaces the HOME git config with credentials only, so enabling it alone leaves no identity and commits fail. Set configure_git explicitly to override: false for an image that keeps its identity in /etc/gitconfig, which the proxy does not touch, or true to sign commits without the proxy.
1 parent a3de94b commit ded9a4a

4 files changed

Lines changed: 141 additions & 0 deletions

File tree

‎registry/coder/modules/agent-relay-claude-code/README.md‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ when to reap the workspace.
4949

5050
## Requirements
5151

52+
- A Coder deployment running 2.37 or newer, which Agent Relay refuses to
53+
start against otherwise.
5254
- The `claude` CLI must be in the workspace. `install_cli` (default `true`)
5355
downloads it at start only when it is not already on PATH; bake it into the
5456
image for the fastest start. `cli_binary` overrides the path.
@@ -146,6 +148,48 @@ rather than reporting `working` forever.
146148

147149
Renaming the `agent_relay_status` key breaks reaping.
148150

151+
## Git authentication
152+
153+
`use_anthropic_git_proxy` authenticates clones server-side, so the
154+
workspace holds no git credentials. A session a person created uses their
155+
GitHub OAuth token; one a bot or agent created uses the organization's
156+
GitHub App installation token.
157+
158+
```tf
159+
module "claude_code_runner" {
160+
# ...
161+
use_anthropic_git_proxy = true
162+
}
163+
```
164+
165+
That second path is the only git auth that works when the workspace owner
166+
is a **service account**, which you detect with `login_type = "none"`.
167+
Such an owner can never sign in, so it can never complete the OAuth flow
168+
a `coder_external_auth` block depends on. Gate on it if you want the
169+
proxy only there; the flag serves human sessions equally well, so
170+
enabling it everywhere is also fine:
171+
172+
```tf
173+
# login_type "none" means a service account: it cannot sign in, so it
174+
# cannot link GitHub.
175+
use_anthropic_git_proxy = data.coder_workspace_owner.me.login_type == "none"
176+
```
177+
178+
`configure_git` follows it by default, setting the identity to
179+
`Claude <noreply@anthropic.com>` with signed commits. The proxy replaces
180+
the HOME git config with credentials only, so without this there is no
181+
identity and commits fail. Override either way:
182+
183+
```tf
184+
configure_git = false # image keeps its identity in /etc/gitconfig
185+
configure_git = true # sign as Claude without the proxy
186+
```
187+
188+
**The proxy rewrites the runner account's git config**, deleting
189+
`~/.gitconfig`, the `GIT_CONFIG_GLOBAL` target and all of
190+
`$XDG_CONFIG_HOME/git` at startup and before every session, with no
191+
backup. Do not enable it on a workspace a person also works in.
192+
149193
## Graceful shutdown
150194

151195
The start step detaches the supervisor with `setsid`, so it lives in its own

‎registry/coder/modules/agent-relay-claude-code/main.tf‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,28 @@ variable "client_label" {
113113
default = ""
114114
}
115115

116+
variable "configure_git" {
117+
description = <<-EOT
118+
Set the global git identity to Claude <noreply@anthropic.com> and sign commits through Anthropic's signing service (the CLI's --configure-git).
119+
120+
Defaults to following use_anthropic_git_proxy: that flag replaces the HOME git config with credentials only, so the proxy alone leaves no identity and commits fail. Set false if your image keeps an identity in /etc/gitconfig, which the proxy does not touch; set true to sign commits without the proxy.
121+
EOT
122+
type = bool
123+
default = null
124+
}
125+
126+
variable "use_anthropic_git_proxy" {
127+
description = <<-EOT
128+
Authenticate clones server-side instead of holding git credentials in the workspace (the CLI's --use-anthropic-git-proxy). A session a person created uses their GitHub OAuth token; one a bot or agent created uses the organization's GitHub App installation token.
129+
130+
That second path is the only one that works when the workspace owner is a service account, which login_type "none" indicates: it can never sign in, so it can never complete a coder_external_auth flow.
131+
132+
DESTRUCTIVE: at startup and before every session the runner deletes ~/.gitconfig, the GIT_CONFIG_GLOBAL target and all of $XDG_CONFIG_HOME/git, with no backup. That isolation is the point for an ephemeral single-session workspace. Do not enable it on a workspace a person also works in, and expect it to override any identity a git-config module wrote.
133+
EOT
134+
type = bool
135+
default = false
136+
}
137+
116138
variable "serving_log_pattern" {
117139
type = string
118140
default = "Picked up session"
@@ -231,6 +253,10 @@ locals {
231253
# tree so one directory holds everything a debugger needs.
232254
module_directory = "$HOME/.coder-modules/coder/agent-relay-claude-code"
233255

256+
# The proxy restores credentials but no identity, so the two travel
257+
# together unless a template says otherwise.
258+
configure_git = var.configure_git != null ? var.configure_git : var.use_anthropic_git_proxy
259+
234260
# coder-utils prefixes its own steps with this; the stop step is a
235261
# plain coder_script, so it joins the same naming by hand.
236262
display_name_prefix = "Claude Code runner"
@@ -251,6 +277,8 @@ locals {
251277
exit_if_unused_min = var.exit_if_unused_min
252278
drain_wait_sec = var.drain_wait_sec
253279
push_outcome_on_release = var.push_outcome_on_release
280+
use_anthropic_git_proxy = var.use_anthropic_git_proxy
281+
configure_git = local.configure_git
254282
# Free-form text: base64 so a label with quotes or spaces can never
255283
# become shell in the supervisor script.
256284
client_label = base64encode(local.client_label)

‎registry/coder/modules/agent-relay-claude-code/main.tftest.hcl‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,13 @@ run "graceful_shutdown_defaults" {
321321
condition = !strcontains(local.start_script, "--push-outcome-on-release") && !strcontains(local.start_script, "--drain-wait-sec")
322322
error_message = "both optional runner behaviours are off by default"
323323
}
324+
325+
# It rewrites the account's HOME git config before every session, so it
326+
# is never on unless the template asked for it.
327+
assert {
328+
condition = !strcontains(local.start_script, "--use-anthropic-git-proxy") && !strcontains(local.start_script, "--configure-git")
329+
error_message = "Anthropic-managed git and the Claude git identity must both be opt-in"
330+
}
324331
}
325332

326333
run "drain_wait_enabled" {
@@ -423,3 +430,59 @@ run "stop_script_waits_for_the_recorded_exit" {
423430
error_message = "the stop script must wait for the supervisor to record the exit, not just for the runner to go"
424431
}
425432
}
433+
434+
run "anthropic_git_proxy_enabled" {
435+
command = plan
436+
437+
variables {
438+
use_anthropic_git_proxy = true
439+
}
440+
441+
assert {
442+
condition = strcontains(local.start_script, "--use-anthropic-git-proxy")
443+
error_message = "use_anthropic_git_proxy must reach the runner as a flag"
444+
}
445+
446+
# The proxy leaves no identity behind, so the two travel together
447+
# unless a template says otherwise.
448+
assert {
449+
condition = strcontains(local.start_script, "--configure-git")
450+
error_message = "the Claude git identity must follow the proxy by default"
451+
}
452+
453+
# It authenticates clones server-side; it does not change what the
454+
# runner has to do on the way out.
455+
assert {
456+
condition = output.shutdown_grace_seconds == 105
457+
error_message = "Anthropic-managed git must not change the shutdown budget"
458+
}
459+
}
460+
461+
run "git_identity_overrides_the_proxy_default" {
462+
command = plan
463+
464+
variables {
465+
use_anthropic_git_proxy = true
466+
configure_git = false
467+
}
468+
469+
# For an image that supplies its identity in /etc/gitconfig, which the
470+
# proxy does not delete.
471+
assert {
472+
condition = strcontains(local.start_script, "--use-anthropic-git-proxy") && !strcontains(local.start_script, "--configure-git")
473+
error_message = "configure_git = false must win over the proxy default"
474+
}
475+
}
476+
477+
run "git_identity_without_the_proxy" {
478+
command = plan
479+
480+
variables {
481+
configure_git = true
482+
}
483+
484+
assert {
485+
condition = strcontains(local.start_script, "--configure-git") && !strcontains(local.start_script, "--use-anthropic-git-proxy")
486+
error_message = "signing commits must not require Anthropic-managed git"
487+
}
488+
}

‎registry/coder/modules/agent-relay-claude-code/start.sh.tftpl‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ ${cli_binary} self-hosted-runner \\
104104
%{ endif ~}
105105
%{ if push_outcome_on_release ~}
106106
--push-outcome-on-release \\
107+
%{ endif ~}
108+
%{ if use_anthropic_git_proxy ~}
109+
--use-anthropic-git-proxy \\
110+
%{ endif ~}
111+
%{ if configure_git ~}
112+
--configure-git \\
107113
%{ endif ~}
108114
--client-label "\$client_label" \\
109115
--exec-path "$wrapper" >"$log_file" 2>&1 &

0 commit comments

Comments
 (0)