From 23c437721ba50340abcf5cac972e4b1620f0c4da Mon Sep 17 00:00:00 2001 From: Brandon Corfman Date: Wed, 29 Jul 2026 20:09:32 -0400 Subject: [PATCH 1/2] Document noninteractive Codex agent invocation --- docs/troubleshooting/repair-harness.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/troubleshooting/repair-harness.md b/docs/troubleshooting/repair-harness.md index b22f3767..baf0ff2c 100644 --- a/docs/troubleshooting/repair-harness.md +++ b/docs/troubleshooting/repair-harness.md @@ -119,11 +119,18 @@ artifact, merge the shard reports, ask Codex for a bounded timing repair, and independently verify it: ```bash -PHASERFORGE_CODEX_COMMAND=codex \\ +export PHASERFORGE_CODEX_COMMAND="$(command -v codex)" +export PHASERFORGE_CODEX_ARGS='["exec","--sandbox","workspace-write"]' + npm run repair:ci -- e2e-timing-repair \\ --pr 123 --agent=codex ``` +If `command -v codex` prints nothing, use the absolute path to the Codex +binary installed by your editor, or add its directory to `PATH`. The harness +passes its diagnosis/implementation packet on stdin, so `codex exec` is +required for non-interactive operation. + Use `--run ` instead of `--pr` when the Actions run is known. The download is automatic; no artifact or `index.html` download is required. `--pr` also works when the E2E check passed, because timing failures are distinct from @@ -133,7 +140,9 @@ verified change should be committed, pushed on an `agent/*` branch, and opened as a draft PR: ```bash -PHASERFORGE_CODEX_COMMAND=codex \\ +export PHASERFORGE_CODEX_COMMAND="$(command -v codex)" +export PHASERFORGE_CODEX_ARGS='["exec","--sandbox","workspace-write"]' + npm run repair:ci -- e2e-timing-repair \\ --pr 123 --agent=codex --publish ``` From 8caf6848898ae2b59285788b5145ec83a0fed012 Mon Sep 17 00:00:00 2001 From: Brandon Corfman Date: Wed, 29 Jul 2026 20:11:50 -0400 Subject: [PATCH 2/2] Simplify automated Codex repair setup --- docs/troubleshooting/repair-harness.md | 16 +++--------- scripts/repair-harness/agent.ts | 34 +++++++++++++++++++++----- scripts/repair-harness/cli.ts | 1 - 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/docs/troubleshooting/repair-harness.md b/docs/troubleshooting/repair-harness.md index baf0ff2c..cb53d03a 100644 --- a/docs/troubleshooting/repair-harness.md +++ b/docs/troubleshooting/repair-harness.md @@ -119,17 +119,12 @@ artifact, merge the shard reports, ask Codex for a bounded timing repair, and independently verify it: ```bash -export PHASERFORGE_CODEX_COMMAND="$(command -v codex)" -export PHASERFORGE_CODEX_ARGS='["exec","--sandbox","workspace-write"]' - npm run repair:ci -- e2e-timing-repair \\ - --pr 123 --agent=codex + --pr 123 ``` -If `command -v codex` prints nothing, use the absolute path to the Codex -binary installed by your editor, or add its directory to `PATH`. The harness -passes its diagnosis/implementation packet on stdin, so `codex exec` is -required for non-interactive operation. +The harness automatically discovers Codex from `PATH` or the VS Code extension +installation and invokes `codex exec` non-interactively. Use `--run ` instead of `--pr` when the Actions run is known. The download is automatic; no artifact or `index.html` download is required. `--pr` @@ -140,11 +135,8 @@ verified change should be committed, pushed on an `agent/*` branch, and opened as a draft PR: ```bash -export PHASERFORGE_CODEX_COMMAND="$(command -v codex)" -export PHASERFORGE_CODEX_ARGS='["exec","--sandbox","workspace-write"]' - npm run repair:ci -- e2e-timing-repair \\ - --pr 123 --agent=codex --publish + --pr 123 --publish ``` The command does not repair warnings (7–10 seconds); only tests over the diff --git a/scripts/repair-harness/agent.ts b/scripts/repair-harness/agent.ts index 9f09bd02..bcc9aefb 100644 --- a/scripts/repair-harness/agent.ts +++ b/scripts/repair-harness/agent.ts @@ -1,4 +1,6 @@ import { spawn } from 'node:child_process'; +import { existsSync, readdirSync } from 'node:fs'; +import path from 'node:path'; import type { RepairDiagnosis } from './types'; @@ -8,14 +10,14 @@ export interface AgentOptions { kind: AgentCallKind; packet: string; cwd: string export interface AgentResult { kind: AgentCallKind; stdout: string; stderr: string; exitCode: number | null; durationMs: number; packetBytes: number; tokenUsage?: { input?: number; output?: number; total?: number }; } /** - * Set PHASERFORGE_CODEX_COMMAND to the local Codex executable (and optionally - * PHASERFORGE_CODEX_ARGS as a JSON string array). The adapter passes the packet - * on stdin and never reads, creates, or embeds credentials. + * The adapter discovers Codex from PATH or the VS Code extension install. The + * environment variables remain available for unusual installations. Packets + * are passed on stdin and credentials are never read, created, or embedded. */ export function runAgent(options: AgentOptions): Promise { - const command = options.command ?? process.env.PHASERFORGE_CODEX_COMMAND; - if (!command) return Promise.reject(new Error('Codex agent is disabled; set PHASERFORGE_CODEX_COMMAND to opt in.')); - let configuredArgs: string[] = []; + const command = options.command ?? process.env.PHASERFORGE_CODEX_COMMAND ?? discoverCodexCommand(); + if (!command) return Promise.reject(new Error('Codex executable not found. Install Codex or add it to PATH.')); + let configuredArgs: string[] = ['exec', '--sandbox', 'workspace-write']; if (options.args) configuredArgs = options.args; else if (process.env.PHASERFORGE_CODEX_ARGS) { try { configuredArgs = JSON.parse(process.env.PHASERFORGE_CODEX_ARGS) as string[]; } catch { return Promise.reject(new Error('PHASERFORGE_CODEX_ARGS must be a JSON string array.')); } @@ -37,6 +39,26 @@ export function runAgent(options: AgentOptions): Promise { }); } +export function discoverCodexCommand(): string | undefined { + const pathResult = spawnSync('which', ['codex'], { encoding: 'utf8' }); + const fromPath = pathResult.status === 0 ? pathResult.stdout.trim() : ''; + if (fromPath) return fromPath; + const home = process.env.HOME; + if (!home) return undefined; + const extensions = path.join(home, '.vscode-server', 'extensions'); + if (!existsSync(extensions)) return undefined; + for (const extension of readdirSync(extensions)) { + if (!extension.startsWith('openai.chatgpt-')) continue; + const binRoot = path.join(extensions, extension, 'bin'); + if (!existsSync(binRoot)) continue; + for (const platform of readdirSync(binRoot)) { + const candidate = path.join(binRoot, platform, 'codex'); + if (existsSync(candidate)) return candidate; + } + } + return undefined; +} + function jsonResponse(text: string): unknown { const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1] ?? text; const start = fenced.indexOf('{'); const end = fenced.lastIndexOf('}'); diff --git a/scripts/repair-harness/cli.ts b/scripts/repair-harness/cli.ts index e6ff060d..67021744 100644 --- a/scripts/repair-harness/cli.ts +++ b/scripts/repair-harness/cli.ts @@ -47,7 +47,6 @@ if (args[0] === 'e2e-timing') { } else if (args[0] === 'e2e-timing-repair') { try { if (!value('--pr') && !value('--run')) throw new Error('Expected --pr or --run for e2e-timing-repair.'); - if (value('--agent') !== 'codex') throw new Error('Automated timing repair requires explicit --agent=codex.'); const result = await runAutomatedTimingRepair({ repo: value('--repo') ?? repositoryRoot, pr: value('--pr'),