From 64701827e3b30cd6c06fdf806d9d24a35f0a9cf5 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Wed, 19 Aug 2026 16:28:09 -0500 Subject: [PATCH 1/5] fix: explain scan sign-in refresh failures --- sdk/typescript/README.md | 14 ++++- sdk/typescript/src/api.ts | 14 ++++- sdk/typescript/src/cli.ts | 7 ++- sdk/typescript/tests-ts/api-events.test.ts | 2 + .../tests-ts/cli-authentication.test.ts | 52 +++++++++++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index 5079dd090..5050da7b2 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -164,7 +164,19 @@ when the dedicated home does not already contain stored credentials. Logging out prevents later scans from automatically reimporting that ambient sign-in until you explicitly log in again. -An environment API key takes precedence over a stored sign-in by default. +If Codex reports that the stored sign-in cannot be refreshed, repair the +sign-in in this dedicated home: + +```bash +npx @openai/codex-security logout +npx @openai/codex-security login +``` + +Codex may need a valid ChatGPT sign-in to load workspace-managed policies even +when an API key is selected. Codex Security does not automatically clear that +sign-in or change managed login restrictions. + +An environment API key takes precedence for model requests by default. When both a stored ChatGPT sign-in and an environment API key are available, an interactive scan asks which credential to use. JSON output, dry runs, CI, and other noninteractive scans never prompt and retain automatic API-key diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 8497fcc97..80cf9e112 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -2435,7 +2435,11 @@ async function readCodexTurn(options: { } else if (event.type === "error" && typeof event["message"] === "string") { const message = event["message"]; const classification = classifyConnectionFailure(message); - if (classification === "unauthorized" || classification === "forbidden") { + if ( + classification === "reauthentication_required" || + classification === "unauthorized" || + classification === "forbidden" + ) { throw new CodexSecurityError(message); } const reconnect = reconnectAttempt(message); @@ -2953,6 +2957,7 @@ function turnFailureMessage(error: unknown): string { export function classifyConnectionFailure( error: unknown, ): + | "reauthentication_required" | "rate_limited" | "unauthorized" | "forbidden" @@ -2963,6 +2968,13 @@ export function classifyConnectionFailure( if (/\b(?:sqlite3?|database|workbench)\b/iu.test(message)) { return "unknown"; } + if ( + /\byour (?:access token|authentication session) could not be refreshed\b/iu.test( + message, + ) + ) { + return "reauthentication_required"; + } if ( /\brate[_ -]?limit(?:ed|[_ -]exceeded)?\b|\b429\b|\btoo many requests\b/iu.test( message, diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index c4e332b73..8e5d7f1c3 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -5178,6 +5178,12 @@ function scanFailureMessage( // the JSON error field. if (isLocalScanFailure(error)) return diagnosticValue(error); switch (classifyConnectionFailure(error)) { + case "reauthentication_required": + return ( + "Codex Security's stored sign-in could not be refreshed. " + + "Codex may need a valid ChatGPT sign-in to load workspace-managed policies, even when an API key is selected. " + + "Run 'npx @openai/codex-security logout', then 'npx @openai/codex-security login', and retry." + ); case "unauthorized": if (authentication?.method === "aws_credentials") { return ( @@ -5187,7 +5193,6 @@ function scanFailureMessage( } return authentication?.method === "api_key" ? `Authentication failed using ${authentication.source}. ` + - "Your ChatGPT sign-in was not used. " + "Retry with '--auth chatgpt' or provide a valid API key." : "Authentication failed using stored ChatGPT credentials. " + "Sign in again with 'codex-security login' or provide a valid API key."; diff --git a/sdk/typescript/tests-ts/api-events.test.ts b/sdk/typescript/tests-ts/api-events.test.ts index 96842f38b..2e8961a6f 100644 --- a/sdk/typescript/tests-ts/api-events.test.ts +++ b/sdk/typescript/tests-ts/api-events.test.ts @@ -897,6 +897,8 @@ describe("one-shot scan events", () => { for (const message of [ "Reconnecting... 1/5 (401 invalid API key org-private)", "Reconnecting... 1/5 (403 model access denied org-private)", + "Reconnecting... 1/5 (Your access token could not be refreshed. Please log out and sign in again.)", + "Reconnecting... 1/5 (Your authentication session could not be refreshed automatically. Please log out and sign in again.)", ]) { const scanDir = join(await temporaryDirectory(), "partial-scan"); await mkdir(scanDir, { mode: 0o700 }); diff --git a/sdk/typescript/tests-ts/cli-authentication.test.ts b/sdk/typescript/tests-ts/cli-authentication.test.ts index e88d3391d..baab31835 100644 --- a/sdk/typescript/tests-ts/cli-authentication.test.ts +++ b/sdk/typescript/tests-ts/cli-authentication.test.ts @@ -884,6 +884,58 @@ describe("CLI authentication", () => { } }); + test("explains stored sign-in recovery regardless of the selected scan credential", async () => { + for (const auth of ["chatgpt", "api-key"] as const) { + for (const detail of [ + "Your access token could not be refreshed.", + "Your access token could not be refreshed because your refresh token has expired.", + "Your authentication session could not be refreshed automatically.", + ]) { + const stdout = capture(); + const stderr = capture(false); + const deps = dependencies({ + environment: { OPENAI_API_KEY: "sk-proj-SYNTHETIC_SECRET_123" }, + }); + const partial = join(stateDirectory, "partial-scan"); + deps.createSecurity = () => ({ + run: async (_repository, options) => { + options?.onOutputDirReady?.(partial); + throw new CodexSecurityError( + `Codex Exec exited with code 1: Error: ${detail} Please log out and sign in again. org-example sk-proj-SYNTHETIC_SECRET_456`, + ); + }, + preflight: async () => fakePreflight(), + close: async () => {}, + }); + + expect( + await main( + ["scan", ".", "--auth", auth, "--json", "--verbose"], + stdout.stream, + stderr.stream, + deps, + ), + ).toBe(2); + expect(stdout.text()).toBe(""); + expect(stderr.text()).toContain("workspace-managed policies"); + expect(stderr.text()).toContain("npx @openai/codex-security logout"); + expect(stderr.text()).toContain("npx @openai/codex-security login"); + expect(stderr.text()).toContain( + 'classification="reauthentication_required"', + ); + expect(stderr.text()).toContain( + `Partial output was kept at ${partial}.`, + ); + expect(stderr.text()).not.toContain("provide a valid API key"); + expect(stderr.text()).not.toContain( + "Your ChatGPT sign-in was not used", + ); + expect(stderr.text()).not.toContain("SYNTHETIC_SECRET"); + expect(stderr.text()).not.toContain("org-example"); + } + } + }); + test("prints the ChatGPT recovery hint on noninteractive scan output", async () => { const stdout = capture(); const stderr = capture(false); From 319a649a1617f26d3110b704bd89ed4f04ffc51e Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Wed, 19 Aug 2026 16:43:07 -0500 Subject: [PATCH 2/5] fix: preserve credential-specific recovery advice --- sdk/typescript/README.md | 5 ++- sdk/typescript/src/api.ts | 2 +- sdk/typescript/src/cli.ts | 2 +- sdk/typescript/tests-ts/api-events.test.ts | 2 +- .../tests-ts/cli-authentication.test.ts | 39 ++++++++++++++++++- 5 files changed, 44 insertions(+), 6 deletions(-) diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index 5050da7b2..cb16cc877 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -164,8 +164,9 @@ when the dedicated home does not already contain stored credentials. Logging out prevents later scans from automatically reimporting that ambient sign-in until you explicitly log in again. -If Codex reports that the stored sign-in cannot be refreshed, repair the -sign-in in this dedicated home: +Check the current sign-in with `npx @openai/codex-security login status`. +If you recently changed accounts, retry the scan before logging out. To replace +an expired stored ChatGPT sign-in in this dedicated home: ```bash npx @openai/codex-security logout diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 80cf9e112..476a7f4ff 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -2969,7 +2969,7 @@ export function classifyConnectionFailure( return "unknown"; } if ( - /\byour (?:access token|authentication session) could not be refreshed\b/iu.test( + /\byour access token could not be refreshed(?: because your refresh token (?:has expired|was already used|was revoked))?\. Please log out and sign in again\./iu.test( message, ) ) { diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index 8e5d7f1c3..5641dbcce 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -5180,7 +5180,7 @@ function scanFailureMessage( switch (classifyConnectionFailure(error)) { case "reauthentication_required": return ( - "Codex Security's stored sign-in could not be refreshed. " + + "Codex Security's stored ChatGPT sign-in could not be refreshed. " + "Codex may need a valid ChatGPT sign-in to load workspace-managed policies, even when an API key is selected. " + "Run 'npx @openai/codex-security logout', then 'npx @openai/codex-security login', and retry." ); diff --git a/sdk/typescript/tests-ts/api-events.test.ts b/sdk/typescript/tests-ts/api-events.test.ts index 2e8961a6f..d04a8a475 100644 --- a/sdk/typescript/tests-ts/api-events.test.ts +++ b/sdk/typescript/tests-ts/api-events.test.ts @@ -898,7 +898,7 @@ describe("one-shot scan events", () => { "Reconnecting... 1/5 (401 invalid API key org-private)", "Reconnecting... 1/5 (403 model access denied org-private)", "Reconnecting... 1/5 (Your access token could not be refreshed. Please log out and sign in again.)", - "Reconnecting... 1/5 (Your authentication session could not be refreshed automatically. Please log out and sign in again.)", + "Reconnecting... 1/5 (Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.)", ]) { const scanDir = join(await temporaryDirectory(), "partial-scan"); await mkdir(scanDir, { mode: 0o700 }); diff --git a/sdk/typescript/tests-ts/cli-authentication.test.ts b/sdk/typescript/tests-ts/cli-authentication.test.ts index baab31835..397ffcd69 100644 --- a/sdk/typescript/tests-ts/cli-authentication.test.ts +++ b/sdk/typescript/tests-ts/cli-authentication.test.ts @@ -889,7 +889,8 @@ describe("CLI authentication", () => { for (const detail of [ "Your access token could not be refreshed.", "Your access token could not be refreshed because your refresh token has expired.", - "Your authentication session could not be refreshed automatically.", + "Your access token could not be refreshed because your refresh token was already used.", + "Your access token could not be refreshed because your refresh token was revoked.", ]) { const stdout = capture(); const stderr = capture(false); @@ -936,6 +937,42 @@ describe("CLI authentication", () => { } }); + test("preserves native recovery advice for account changes and access-token failures", async () => { + for (const [message, environment] of [ + [ + "Your access token could not be refreshed because you have since logged out or signed in to another account. Please sign in again.", + {}, + ], + [ + "Your authentication session could not be refreshed automatically. Please log out and sign in again.", + {}, + ], + [ + "Your authentication session could not be refreshed automatically. Please log out and sign in again.", + { CODEX_ACCESS_TOKEN: "SYNTHETIC_ACCESS_TOKEN" }, + ], + ] as const) { + const stdout = capture(); + const stderr = capture(false); + const deps = dependencies({ environment }); + deps.createSecurity = () => ({ + run: async () => { + throw new CodexSecurityError(message); + }, + preflight: async () => fakePreflight(), + close: async () => {}, + }); + + expect( + await main(["scan", "--json"], stdout.stream, stderr.stream, deps), + ).toBe(2); + expect(stdout.text()).toBe(""); + expect(stderr.text()).toContain(`${message}\n`); + expect(stderr.text()).not.toContain("npx @openai/codex-security logout"); + expect(stderr.text()).not.toContain("SYNTHETIC_ACCESS_TOKEN"); + } + }); + test("prints the ChatGPT recovery hint on noninteractive scan output", async () => { const stdout = capture(); const stderr = capture(false); From b896475cda13b60eddb9ad8f1e6e11a23deb5389 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Wed, 19 Aug 2026 16:56:33 -0500 Subject: [PATCH 3/5] refactor: leave native auth recovery to Codex --- sdk/typescript/src/api.ts | 14 +--------- sdk/typescript/src/cli.ts | 18 ++++++++----- sdk/typescript/tests-ts/api-events.test.ts | 2 -- .../tests-ts/cli-authentication.test.ts | 27 +++++++++---------- 4 files changed, 25 insertions(+), 36 deletions(-) diff --git a/sdk/typescript/src/api.ts b/sdk/typescript/src/api.ts index 476a7f4ff..8497fcc97 100644 --- a/sdk/typescript/src/api.ts +++ b/sdk/typescript/src/api.ts @@ -2435,11 +2435,7 @@ async function readCodexTurn(options: { } else if (event.type === "error" && typeof event["message"] === "string") { const message = event["message"]; const classification = classifyConnectionFailure(message); - if ( - classification === "reauthentication_required" || - classification === "unauthorized" || - classification === "forbidden" - ) { + if (classification === "unauthorized" || classification === "forbidden") { throw new CodexSecurityError(message); } const reconnect = reconnectAttempt(message); @@ -2957,7 +2953,6 @@ function turnFailureMessage(error: unknown): string { export function classifyConnectionFailure( error: unknown, ): - | "reauthentication_required" | "rate_limited" | "unauthorized" | "forbidden" @@ -2968,13 +2963,6 @@ export function classifyConnectionFailure( if (/\b(?:sqlite3?|database|workbench)\b/iu.test(message)) { return "unknown"; } - if ( - /\byour access token could not be refreshed(?: because your refresh token (?:has expired|was already used|was revoked))?\. Please log out and sign in again\./iu.test( - message, - ) - ) { - return "reauthentication_required"; - } if ( /\brate[_ -]?limit(?:ed|[_ -]exceeded)?\b|\b429\b|\btoo many requests\b/iu.test( message, diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index 5641dbcce..fb5d2ef96 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -5177,13 +5177,19 @@ function scanFailureMessage( // errors can name the organization or project, which must not reach stderr or // the JSON error field. if (isLocalScanFailure(error)) return diagnosticValue(error); + if ( + /\byour access token could not be refreshed(?: because your refresh token (?:has expired|was already used|was revoked))?\. Please log out and sign in again\./iu.test( + errorMessage(error), + ) + ) { + return ( + "Codex Security's stored ChatGPT sign-in could not be refreshed. " + + "Codex may need it to load workspace-managed policies, even when an API key is selected. " + + "Check 'npx @openai/codex-security login status' and retry if the sign-in has changed. " + + "To replace a stale ChatGPT sign-in, run 'npx @openai/codex-security logout', then 'npx @openai/codex-security login'." + ); + } switch (classifyConnectionFailure(error)) { - case "reauthentication_required": - return ( - "Codex Security's stored ChatGPT sign-in could not be refreshed. " + - "Codex may need a valid ChatGPT sign-in to load workspace-managed policies, even when an API key is selected. " + - "Run 'npx @openai/codex-security logout', then 'npx @openai/codex-security login', and retry." - ); case "unauthorized": if (authentication?.method === "aws_credentials") { return ( diff --git a/sdk/typescript/tests-ts/api-events.test.ts b/sdk/typescript/tests-ts/api-events.test.ts index d04a8a475..96842f38b 100644 --- a/sdk/typescript/tests-ts/api-events.test.ts +++ b/sdk/typescript/tests-ts/api-events.test.ts @@ -897,8 +897,6 @@ describe("one-shot scan events", () => { for (const message of [ "Reconnecting... 1/5 (401 invalid API key org-private)", "Reconnecting... 1/5 (403 model access denied org-private)", - "Reconnecting... 1/5 (Your access token could not be refreshed. Please log out and sign in again.)", - "Reconnecting... 1/5 (Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.)", ]) { const scanDir = join(await temporaryDirectory(), "partial-scan"); await mkdir(scanDir, { mode: 0o700 }); diff --git a/sdk/typescript/tests-ts/cli-authentication.test.ts b/sdk/typescript/tests-ts/cli-authentication.test.ts index 397ffcd69..72e05bed9 100644 --- a/sdk/typescript/tests-ts/cli-authentication.test.ts +++ b/sdk/typescript/tests-ts/cli-authentication.test.ts @@ -894,19 +894,17 @@ describe("CLI authentication", () => { ]) { const stdout = capture(); const stderr = capture(false); + const partial = join(stateDirectory, "partial-scan"); const deps = dependencies({ environment: { OPENAI_API_KEY: "sk-proj-SYNTHETIC_SECRET_123" }, - }); - const partial = join(stateDirectory, "partial-scan"); - deps.createSecurity = () => ({ - run: async (_repository, options) => { - options?.onOutputDirReady?.(partial); + onTurn: (_repository, options) => { + (options as ScanOptions).onOutputDirReady?.(partial); + }, + onRun: () => { throw new CodexSecurityError( `Codex Exec exited with code 1: Error: ${detail} Please log out and sign in again. org-example sk-proj-SYNTHETIC_SECRET_456`, ); }, - preflight: async () => fakePreflight(), - close: async () => {}, }); expect( @@ -919,11 +917,12 @@ describe("CLI authentication", () => { ).toBe(2); expect(stdout.text()).toBe(""); expect(stderr.text()).toContain("workspace-managed policies"); - expect(stderr.text()).toContain("npx @openai/codex-security logout"); - expect(stderr.text()).toContain("npx @openai/codex-security login"); expect(stderr.text()).toContain( - 'classification="reauthentication_required"', + "npx @openai/codex-security login status", ); + expect(stderr.text()).toContain("retry if the sign-in has changed"); + expect(stderr.text()).toContain("npx @openai/codex-security logout"); + expect(stderr.text()).toContain("npx @openai/codex-security login"); expect(stderr.text()).toContain( `Partial output was kept at ${partial}.`, ); @@ -954,13 +953,11 @@ describe("CLI authentication", () => { ] as const) { const stdout = capture(); const stderr = capture(false); - const deps = dependencies({ environment }); - deps.createSecurity = () => ({ - run: async () => { + const deps = dependencies({ + environment, + onRun: () => { throw new CodexSecurityError(message); }, - preflight: async () => fakePreflight(), - close: async () => {}, }); expect( From 79d3382de1eaa69b6e8718212d738e3c9a9181b2 Mon Sep 17 00:00:00 2001 From: Michael D'Angelo Date: Thu, 20 Aug 2026 16:29:34 -0700 Subject: [PATCH 4/5] refactor: simplify scan sign-in recovery --- sdk/typescript/README.md | 26 ++++------ sdk/typescript/src/cli.ts | 6 +-- .../tests-ts/cli-authentication.test.ts | 47 ++++++------------- 3 files changed, 27 insertions(+), 52 deletions(-) diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index cb16cc877..bad2fff74 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -149,9 +149,8 @@ $env:OPENAI_API_KEY = "" npx @openai/codex-security scan C:\code\repository ``` -Check or remove the stored sign-in with `npx @openai/codex-security login status` -and `npx @openai/codex-security logout`. Codex Security keeps its sign-in in a -private, stable Codex home at `$CODEX_SECURITY_STATE_DIR/codex-home`, or at +Codex Security keeps its sign-in in a private, stable Codex home at +`$CODEX_SECURITY_STATE_DIR/codex-home`, or at `$CODEX_HOME/state/plugins/codex-security/codex-home` when no state directory is configured. On managed Windows devices, inherited access for `SYSTEM` and local `Administrators` is preserved while protecting the home against future changes @@ -164,20 +163,15 @@ when the dedicated home does not already contain stored credentials. Logging out prevents later scans from automatically reimporting that ambient sign-in until you explicitly log in again. -Check the current sign-in with `npx @openai/codex-security login status`. -If you recently changed accounts, retry the scan before logging out. To replace -an expired stored ChatGPT sign-in in this dedicated home: +If a scan says the stored ChatGPT sign-in could not be refreshed, check it with +`npx @openai/codex-security login status` and retry if it recently changed. +Otherwise replace it with `npx @openai/codex-security logout`, then +`npx @openai/codex-security login`. Codex Security does not automatically clear +the sign-in or change managed login restrictions. -```bash -npx @openai/codex-security logout -npx @openai/codex-security login -``` - -Codex may need a valid ChatGPT sign-in to load workspace-managed policies even -when an API key is selected. Codex Security does not automatically clear that -sign-in or change managed login restrictions. - -An environment API key takes precedence for model requests by default. +An environment API key takes precedence for model authentication by default, +but Codex may still need a valid ChatGPT sign-in to load workspace-managed +policies. When both a stored ChatGPT sign-in and an environment API key are available, an interactive scan asks which credential to use. JSON output, dry runs, CI, and other noninteractive scans never prompt and retain automatic API-key diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index fb5d2ef96..02acf5a9d 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -5184,9 +5184,9 @@ function scanFailureMessage( ) { return ( "Codex Security's stored ChatGPT sign-in could not be refreshed. " + - "Codex may need it to load workspace-managed policies, even when an API key is selected. " + - "Check 'npx @openai/codex-security login status' and retry if the sign-in has changed. " + - "To replace a stale ChatGPT sign-in, run 'npx @openai/codex-security logout', then 'npx @openai/codex-security login'." + "Codex may still need it to load workspace-managed policies when an API key is selected for model authentication. " + + "If the sign-in recently changed, check 'npx @openai/codex-security login status' and retry. " + + "Otherwise run 'npx @openai/codex-security logout', then 'npx @openai/codex-security login'." ); } switch (classifyConnectionFailure(error)) { diff --git a/sdk/typescript/tests-ts/cli-authentication.test.ts b/sdk/typescript/tests-ts/cli-authentication.test.ts index 72e05bed9..06f0f2eb2 100644 --- a/sdk/typescript/tests-ts/cli-authentication.test.ts +++ b/sdk/typescript/tests-ts/cli-authentication.test.ts @@ -878,13 +878,14 @@ describe("CLI authentication", () => { expect(stderr.text()).toContain(expected); expect(stderr.text()).toContain(source); expect(stderr.text()).toContain("--auth chatgpt"); + expect(stderr.text()).not.toContain("ChatGPT sign-in was not used"); expect(stderr.text()).not.toContain("SYNTHETIC_SECRET"); expect(stderr.text()).not.toContain("org-private"); } } }); - test("explains stored sign-in recovery regardless of the selected scan credential", async () => { + test("replaces permanent stored sign-in refresh details with recovery steps", async () => { for (const auth of ["chatgpt", "api-key"] as const) { for (const detail of [ "Your access token could not be refreshed.", @@ -894,22 +895,18 @@ describe("CLI authentication", () => { ]) { const stdout = capture(); const stderr = capture(false); - const partial = join(stateDirectory, "partial-scan"); const deps = dependencies({ environment: { OPENAI_API_KEY: "sk-proj-SYNTHETIC_SECRET_123" }, - onTurn: (_repository, options) => { - (options as ScanOptions).onOutputDirReady?.(partial); - }, onRun: () => { throw new CodexSecurityError( - `Codex Exec exited with code 1: Error: ${detail} Please log out and sign in again. org-example sk-proj-SYNTHETIC_SECRET_456`, + `Codex Exec exited with code 1: Error: ${detail} Please log out and sign in again. PRIVATE_UPSTREAM_DETAIL`, ); }, }); expect( await main( - ["scan", ".", "--auth", auth, "--json", "--verbose"], + ["scan", ".", "--auth", auth, "--json"], stdout.stream, stderr.stream, deps, @@ -917,44 +914,29 @@ describe("CLI authentication", () => { ).toBe(2); expect(stdout.text()).toBe(""); expect(stderr.text()).toContain("workspace-managed policies"); + expect(stderr.text()).toContain( + "API key is selected for model authentication", + ); expect(stderr.text()).toContain( "npx @openai/codex-security login status", ); - expect(stderr.text()).toContain("retry if the sign-in has changed"); - expect(stderr.text()).toContain("npx @openai/codex-security logout"); - expect(stderr.text()).toContain("npx @openai/codex-security login"); expect(stderr.text()).toContain( - `Partial output was kept at ${partial}.`, + "npx @openai/codex-security logout', then 'npx @openai/codex-security login", ); expect(stderr.text()).not.toContain("provide a valid API key"); - expect(stderr.text()).not.toContain( - "Your ChatGPT sign-in was not used", - ); - expect(stderr.text()).not.toContain("SYNTHETIC_SECRET"); - expect(stderr.text()).not.toContain("org-example"); + expect(stderr.text()).not.toContain("PRIVATE_UPSTREAM_DETAIL"); } } }); - test("preserves native recovery advice for account changes and access-token failures", async () => { - for (const [message, environment] of [ - [ - "Your access token could not be refreshed because you have since logged out or signed in to another account. Please sign in again.", - {}, - ], - [ - "Your authentication session could not be refreshed automatically. Please log out and sign in again.", - {}, - ], - [ - "Your authentication session could not be refreshed automatically. Please log out and sign in again.", - { CODEX_ACCESS_TOKEN: "SYNTHETIC_ACCESS_TOKEN" }, - ], - ] as const) { + test("leaves other sign-in recovery messages unchanged", async () => { + for (const message of [ + "Your access token could not be refreshed because you have since logged out or signed in to another account. Please sign in again.", + "Your authentication session could not be refreshed automatically. Please log out and sign in again.", + ]) { const stdout = capture(); const stderr = capture(false); const deps = dependencies({ - environment, onRun: () => { throw new CodexSecurityError(message); }, @@ -966,7 +948,6 @@ describe("CLI authentication", () => { expect(stdout.text()).toBe(""); expect(stderr.text()).toContain(`${message}\n`); expect(stderr.text()).not.toContain("npx @openai/codex-security logout"); - expect(stderr.text()).not.toContain("SYNTHETIC_ACCESS_TOKEN"); } }); From 106ee4bfd5fd7cfc08f6d4ca2418f8d18613f948 Mon Sep 17 00:00:00 2001 From: mldangelo-oai <269034524+mldangelo-oai@users.noreply.github.com> Date: Sat, 22 Aug 2026 14:52:51 +0000 Subject: [PATCH 5/5] fix: sanitize native sign-in recovery errors --- sdk/typescript/src/cli.ts | 7 ++++++- sdk/typescript/tests-ts/cli-authentication.test.ts | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/sdk/typescript/src/cli.ts b/sdk/typescript/src/cli.ts index 3e20b67e4..e879156bd 100644 --- a/sdk/typescript/src/cli.ts +++ b/sdk/typescript/src/cli.ts @@ -5740,9 +5740,14 @@ function scanFailureMessage( // errors can name the organization or project, which must not reach stderr or // the JSON error field. if (isLocalScanFailure(error)) return diagnosticValue(error); + const message = errorMessage(error); + const nativeRefreshRecovery = message.match( + /\b(?:your access token could not be refreshed because you have since logged out or signed in to another account\. Please sign in again\.|your authentication session could not be refreshed automatically\. Please log out and sign in again\.)/iu, + )?.[0]; + if (nativeRefreshRecovery !== undefined) return nativeRefreshRecovery; if ( /\byour access token could not be refreshed(?: because your refresh token (?:has expired|was already used|was revoked))?\. Please log out and sign in again\./iu.test( - errorMessage(error), + message, ) ) { return ( diff --git a/sdk/typescript/tests-ts/cli-authentication.test.ts b/sdk/typescript/tests-ts/cli-authentication.test.ts index 06f0f2eb2..0d55872bd 100644 --- a/sdk/typescript/tests-ts/cli-authentication.test.ts +++ b/sdk/typescript/tests-ts/cli-authentication.test.ts @@ -938,7 +938,9 @@ describe("CLI authentication", () => { const stderr = capture(false); const deps = dependencies({ onRun: () => { - throw new CodexSecurityError(message); + throw new CodexSecurityError( + `Codex Exec exited with code 1: ${message} PRIVATE_UPSTREAM_DETAIL`, + ); }, }); @@ -947,6 +949,7 @@ describe("CLI authentication", () => { ).toBe(2); expect(stdout.text()).toBe(""); expect(stderr.text()).toContain(`${message}\n`); + expect(stderr.text()).not.toContain("PRIVATE_UPSTREAM_DETAIL"); expect(stderr.text()).not.toContain("npx @openai/codex-security logout"); } });