From d16a947831c6b191764a4ce99ed6825427b37fc8 Mon Sep 17 00:00:00 2001 From: Tim Disney Date: Thu, 23 Jul 2026 12:04:04 -0700 Subject: [PATCH] wire oauth creds --- docker/Dockerfile | 3 ++- packages/daemon/README.md | 6 ++++-- packages/daemon/src/cli.ts | 1 + packages/daemon/src/turn.ts | 17 +++++++++++++++-- packages/daemon/test/turn.test.mjs | 7 +++++-- readme.md | 10 +++++++--- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 39cb4c0..6a42506 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -19,7 +19,8 @@ # `inject-workspace-packages=true`, which this workspace does not set.) # - The daemon supplies mounts (/work rw for implementation turns and ro otherwise, /bundle ro, # /run/radial rw), env -# (RADIAL_SIDECAR_SOCKET, RADIAL_TURN_TOKEN, ANTHROPIC_API_KEY, GH_TOKEN — see +# (RADIAL_SIDECAR_SOCKET, RADIAL_TURN_TOKEN, ANTHROPIC_API_KEY, +# CLAUDE_CODE_OAUTH_TOKEN, GH_TOKEN — see # turn.ts), and the argv (`claude -p ...`, see harness.ts) at # `docker run` time. This image deliberately sets no ENTRYPOINT/CMD and no opinionated WORKDIR # (turn.ts passes `-w /work` on every run). diff --git a/packages/daemon/README.md b/packages/daemon/README.md index 29e2955..197e5ef 100644 --- a/packages/daemon/README.md +++ b/packages/daemon/README.md @@ -13,8 +13,10 @@ fine-grained PAT may instead be supplied as `GH_TOKEN` (legacy `GITHUB_TOKEN` is are never stored in radial configuration or protocol records. Keep `GH_TOKEN` set for the daemon; it is injected only into implementation turns and is also used for observation polling. -Use a dedicated, spend-capped `ANTHROPIC_API_KEY` for turns. Check containers are deliberately -secret-free (`env: {}`) and may use normal network access for dependency installation. +For Claude turns, provide either a dedicated, spend-capped `ANTHROPIC_API_KEY` or +`CLAUDE_CODE_OAUTH_TOKEN` in the daemon environment. The selected credential is forwarded only to +turn containers; check containers are deliberately secret-free (`env: {}`) and may use normal +network access for dependency installation. ## Direct PR workflow diff --git a/packages/daemon/src/cli.ts b/packages/daemon/src/cli.ts index dc17b58..a671b90 100644 --- a/packages/daemon/src/cli.ts +++ b/packages/daemon/src/cli.ts @@ -370,6 +370,7 @@ async function runCommand(args: string[]): Promise { runner, harness, ...(process.env.ANTHROPIC_API_KEY ? { anthropicApiKey: process.env.ANTHROPIC_API_KEY } : {}), + ...(process.env.CLAUDE_CODE_OAUTH_TOKEN ? { claudeCodeOauthToken: process.env.CLAUDE_CODE_OAUTH_TOKEN } : {}), ...(githubToken ? { githubToken } : {}), log: (message) => console.log(message), }) diff --git a/packages/daemon/src/turn.ts b/packages/daemon/src/turn.ts index af7b05c..a805c2f 100644 --- a/packages/daemon/src/turn.ts +++ b/packages/daemon/src/turn.ts @@ -64,6 +64,8 @@ export interface TurnDeps { now?: () => string makeToken?: () => string anthropicApiKey?: string + /** Claude Code OAuth credential, forwarded only to the turn container. */ + claudeCodeOauthToken?: string githubToken?: string log?: (message: string) => void } @@ -229,6 +231,7 @@ export async function runTurn(input: TurnInput, deps: TurnDeps): Promise { + await saveTurnError(input, error, [ + token, + deps.anthropicApiKey ?? '', + deps.claudeCodeOauthToken ?? '', + deps.githubToken ?? '', + ]).catch((logError: unknown) => { deps.log?.(`failed to write turn diagnostic log ${input.logPath}: ${logError instanceof Error ? logError.message : String(logError)}`) }) throw error diff --git a/packages/daemon/test/turn.test.mjs b/packages/daemon/test/turn.test.mjs index 59dcb95..c365455 100644 --- a/packages/daemon/test/turn.test.mjs +++ b/packages/daemon/test/turn.test.mjs @@ -183,6 +183,7 @@ it('implementation turn injects only operator GitHub/model credentials and synch )}` assert.equal(spec.env.GH_TOKEN, 'github-token') assert.equal(spec.env.ANTHROPIC_API_KEY, 'model-token') + assert.equal(spec.env.CLAUDE_CODE_OAUTH_TOKEN, 'oauth-token') assert.equal(spec.env.RADIAL_ARTIFACT_URI, expectedUri) assert.equal(spec.env.RADIAL_BRANCH, branch) assert.equal(spec.env.RADIAL_BASE_BRANCH, 'main') @@ -222,6 +223,7 @@ it('implementation turn injects only operator GitHub/model credentials and synch checkout: noopCheckout, githubToken: 'github-token', anthropicApiKey: 'model-token', + claudeCodeOauthToken: 'oauth-token', }, ) @@ -245,7 +247,7 @@ it('crashed: a container that produces no socket observation is classified crash exitCode: 1, timedOut: false, stdout: 'agent output', - stderr: 'failed with token secret-token', + stderr: 'failed with tokens secret-token and oauth-credential', })) const messages = [] const result = await runTurn(baseInput(runDir, { actor, logPath }), { @@ -253,6 +255,7 @@ it('crashed: a container that produces no socket observation is classified crash harness: new ClaudeCodeHarness(), checkout: noopCheckout, makeToken: () => 'secret-token', + claudeCodeOauthToken: 'oauth-credential', log: (message) => messages.push(message), }) assert.equal(result.outcome, 'crashed') @@ -260,7 +263,7 @@ it('crashed: a container that produces no socket observation is classified crash const log = await readFile(logPath, 'utf8') assert.match(log, /exit_code: 1/) assert.match(log, /agent output/) - assert.match(log, /failed with token \[redacted\]/) + assert.match(log, /failed with tokens \[redacted\] and \[redacted\]/) assert.doesNotMatch(log, /secret-token/) assert.match(messages[0], /exited without an artifact or question/) assert.match(messages[0], new RegExp(logPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) diff --git a/readme.md b/readme.md index 41d395d..7889a98 100644 --- a/readme.md +++ b/readme.md @@ -43,15 +43,19 @@ account for the agent and a separate existing account for the human operator. The daemon currently launches the Claude Code CLI inside Docker, so the agent profile below uses `claude` and an Anthropic model. -Before starting, install Docker, authenticate the GitHub CLI if implementation -turns will push branches and open PRs, and provide a dedicated, spend-capped -`ANTHROPIC_API_KEY`. Build the turn image after building the workspace: +Before starting, install Docker and authenticate the GitHub CLI if implementation +turns will push branches and open PRs. For Claude, provide either a dedicated, +spend-capped `ANTHROPIC_API_KEY` or a `CLAUDE_CODE_OAUTH_TOKEN`; the daemon +forwards either credential only to turn containers. Build the turn image after +building the workspace: ```sh pnpm build pnpm images gh auth login # needed for implementation turns export ANTHROPIC_API_KEY='...' +# Or, if your Claude Code subscription is authenticated with OAuth: +export CLAUDE_CODE_OAUTH_TOKEN='...' ``` Create the agent configuration. This prompts for an existing agent handle (or -- 2.51.2