From dae6f0974ebab5a5931dfb7720e368a6b2eeee15 Mon Sep 17 00:00:00 2001 From: Cameron Beeley Date: Wed, 3 Jun 2026 22:06:23 +0100 Subject: [PATCH] feat: centralized CLAWSWEEPER_CODEX_LOGIN_METHOD with validation Centralize forced_login_method override into a shared helper in codex-env.ts with validation (only 'api' and 'chatgpt' accepted, invalid values fall back to 'api'). Applied to all 4 Codex spawn sites: - clawsweeper.ts runCodex() - clawsweeper.ts runCodexAssist() - commit-sweeper.ts - pr-close-coverage-proof.ts Tests: default api, chatgpt override, invalid rejection, config string format. --- src/clawsweeper.ts | 8 +++--- src/codex-env.ts | 14 ++++++++++ src/commit-sweeper.ts | 4 +-- src/pr-close-coverage-proof.ts | 4 +-- test/clawsweeper.test.ts | 50 ++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/clawsweeper.ts b/src/clawsweeper.ts index 64a0ef2cb6..e7f986ccf0 100644 --- a/src/clawsweeper.ts +++ b/src/clawsweeper.ts @@ -24,7 +24,7 @@ import { repositoryProfileForSlug, type RepositoryProfile, } from "./repository-profiles.js"; -import { codexEnv } from "./codex-env.js"; +import { codexEnv, codexLoginMethodConfig } from "./codex-env.js"; import { ghRetryKind, ghRetryWaitMs, @@ -66,7 +66,7 @@ import { } from "./clawsweeper-args.js"; import { escapeRegExp, safeOutputTail, trimMiddle, truncateText } from "./clawsweeper-text.js"; -export { codexEnv } from "./codex-env.js"; +export { codexEnv, resolveCodexLoginMethod, codexLoginMethodConfig } from "./codex-env.js"; export { parseGhJson, parseGhJsonLines } from "./github-json.js"; export { itemNumbersArg } from "./clawsweeper-args.js"; export { safeOutputTail } from "./clawsweeper-text.js"; @@ -6174,7 +6174,7 @@ function runCodex(options: { } const codexConfig = [ `model_reasoning_effort="${options.reasoningEffort}"`, - 'forced_login_method="api"', + codexLoginMethodConfig(), 'approval_policy="never"', ]; if (options.serviceTier) codexConfig.splice(1, 0, `service_tier="${options.serviceTier}"`); @@ -6434,7 +6434,7 @@ function runCodexAssist(options: { writeFileSync(promptPath, prompt, "utf8"); const codexConfig = [ `model_reasoning_effort="${options.reasoningEffort}"`, - 'forced_login_method="api"', + codexLoginMethodConfig(), 'approval_policy="never"', ]; const result = spawnSync( diff --git a/src/codex-env.ts b/src/codex-env.ts index adb62cf131..5026e41a70 100644 --- a/src/codex-env.ts +++ b/src/codex-env.ts @@ -1,3 +1,17 @@ +const VALID_LOGIN_METHODS = new Set(["api", "chatgpt"]); + +export function resolveCodexLoginMethod(): string { + const value = process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD?.trim().toLowerCase(); + if (value && VALID_LOGIN_METHODS.has(value)) { + return value; + } + return "api"; +} + +export function codexLoginMethodConfig(): string { + return `forced_login_method="${resolveCodexLoginMethod()}"`; +} + export type CodexEnvOptions = { ghToken?: string | undefined; }; diff --git a/src/commit-sweeper.ts b/src/commit-sweeper.ts index 28a91f29be..a813d6996b 100644 --- a/src/commit-sweeper.ts +++ b/src/commit-sweeper.ts @@ -11,7 +11,7 @@ import { import { publishCheckFromReport, splitFrontMatter } from "./commit-checks.js"; import { argBool, argNumber, argString, parseArgs, type Args } from "./clawsweeper-args.js"; import { safeOutputTail } from "./clawsweeper-text.js"; -import { codexEnv } from "./codex-env.js"; +import { codexEnv, codexLoginMethodConfig } from "./codex-env.js"; import { runText } from "./command.js"; import { ghRetryKind, ghRetryWaitMs } from "./github-retry.js"; import { DEFAULT_TARGET_REPO, repositoryProfileFor } from "./repository-profiles.js"; @@ -296,7 +296,7 @@ function runCodex(options: { ); const codexConfig = [ `model_reasoning_effort="${options.reasoningEffort}"`, - 'forced_login_method="api"', + codexLoginMethodConfig(), 'approval_policy="never"', ]; if (options.serviceTier) codexConfig.splice(1, 0, `service_tier="${options.serviceTier}"`); diff --git a/src/pr-close-coverage-proof.ts b/src/pr-close-coverage-proof.ts index 0b6f08685a..8c033a45d4 100644 --- a/src/pr-close-coverage-proof.ts +++ b/src/pr-close-coverage-proof.ts @@ -1,7 +1,7 @@ import { spawnSync } from "node:child_process"; import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs"; import { join } from "node:path"; -import { codexEnv } from "./codex-env.js"; +import { codexEnv, codexLoginMethodConfig } from "./codex-env.js"; import { safeOutputTail, truncateText } from "./clawsweeper-text.js"; export type PrCloseCoverageProofModelDecision = "covered" | "keep_open"; @@ -252,7 +252,7 @@ export function runPrCloseCoverageProofModel(options: { if (existsSync(outputPath)) unlinkSync(outputPath); const codexConfig = [ `model_reasoning_effort="${options.runtime.reasoningEffort}"`, - 'forced_login_method="api"', + codexLoginMethodConfig(), 'approval_policy="never"', ]; if (options.runtime.serviceTier) { diff --git a/test/clawsweeper.test.ts b/test/clawsweeper.test.ts index bc2a79b84a..eb2766fd4d 100644 --- a/test/clawsweeper.test.ts +++ b/test/clawsweeper.test.ts @@ -34,6 +34,8 @@ import { referencingMergedPullRequestsForIssueForTest, configSurfaceChangeFromPullFilesForTest, codexEnv, + resolveCodexLoginMethod, + codexLoginMethodConfig, dashboardClosedAt, extractLatestClawSweeperReviewForTest, filterReviewContextCommentsForTest, @@ -16727,6 +16729,54 @@ test("codex subprocess env can expose an explicit read-only GitHub token", () => } }); +test("resolveCodexLoginMethod defaults to api", () => { + const original = process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + try { + delete process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + assert.equal(resolveCodexLoginMethod(), "api"); + } finally { + if (original === undefined) delete process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + else process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD = original; + } +}); + +test("resolveCodexLoginMethod accepts chatgpt", () => { + const original = process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + try { + process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD = "chatgpt"; + assert.equal(resolveCodexLoginMethod(), "chatgpt"); + } finally { + if (original === undefined) delete process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + else process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD = original; + } +}); + +test("resolveCodexLoginMethod rejects invalid values", () => { + const original = process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + try { + process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD = "oauth-browser"; + assert.equal(resolveCodexLoginMethod(), "api"); + process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD = ""; + assert.equal(resolveCodexLoginMethod(), "api"); + } finally { + if (original === undefined) delete process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + else process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD = original; + } +}); + +test("codexLoginMethodConfig produces valid Codex config string", () => { + const original = process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + try { + delete process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + assert.equal(codexLoginMethodConfig(), 'forced_login_method="api"'); + process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD = "chatgpt"; + assert.equal(codexLoginMethodConfig(), 'forced_login_method="chatgpt"'); + } finally { + if (original === undefined) delete process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD; + else process.env.CLAWSWEEPER_CODEX_LOGIN_METHOD = original; + } +}); + test("related title search terms keep issue-specific words", () => { assert.deepEqual( relatedTitleSearchTerms(