Skip to content

Commit 0e0ffc8

Browse files
committed
fix: clarify manual callback fallback messaging
1 parent 14b5ef5 commit 0e0ffc8

2 files changed

Lines changed: 62 additions & 6 deletions

File tree

lib/codex-manager.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,7 +1529,8 @@ async function runOAuthFlow(
15291529
);
15301530
}
15311531

1532-
if (oauthServer?.ready) {
1532+
const waitingForCallback = !preferManualMode && oauthServer?.ready === true;
1533+
if (waitingForCallback && oauthServer) {
15331534
console.log(stylePromptText(UI_COPY.oauth.waitingCallback, "muted"));
15341535
const callbackResult = await oauthServer.waitForCode(state);
15351536
code = callbackResult?.code ?? null;
@@ -1538,7 +1539,9 @@ async function runOAuthFlow(
15381539
if (!code) {
15391540
console.log(
15401541
stylePromptText(
1541-
oauthServer?.ready ? UI_COPY.oauth.callbackMissed : UI_COPY.oauth.callbackUnavailable,
1542+
waitingForCallback
1543+
? UI_COPY.oauth.callbackMissed
1544+
: UI_COPY.oauth.callbackUnavailable,
15421545
"warning",
15431546
),
15441547
);

test/codex-manager-cli.test.ts

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2953,9 +2953,9 @@ describe("codex manager cli commands", () => {
29532953
expect(withAccountStorageTransactionMock).toHaveBeenCalledTimes(1);
29542954
expect(storageState.accounts).toHaveLength(2);
29552955
expect(storageState.activeIndex).toBe(1);
2956-
expect(storageState.activeIndexByFamily.codex).toBe(1);
2957-
expect(setCodexCliActiveSelectionMock).toHaveBeenCalledTimes(1);
2958-
});
2956+
expect(storageState.activeIndexByFamily.codex).toBe(1);
2957+
expect(setCodexCliActiveSelectionMock).toHaveBeenCalledTimes(1);
2958+
});
29592959

29602960
it("supports --manual login without launching a browser", async () => {
29612961
setInteractiveTTY(true);
@@ -2991,17 +2991,28 @@ describe("codex manager cli commands", () => {
29912991
const browserModule = await import("../lib/auth/browser.js");
29922992
const openBrowserUrlMock = vi.mocked(browserModule.openBrowserUrl);
29932993
const serverModule = await import("../lib/auth/server.js");
2994+
const waitForCodeMock = vi.fn(async () => ({ code: "oauth-code" }));
29942995
vi.mocked(serverModule.startLocalOAuthServer).mockResolvedValueOnce({
29952996
ready: true,
2996-
waitForCode: vi.fn(async () => ({ code: "oauth-code" })),
2997+
waitForCode: waitForCodeMock,
29972998
close: vi.fn(),
29982999
});
3000+
promptQuestionMock.mockResolvedValueOnce(
3001+
"http://127.0.0.1:1455/auth/callback?code=oauth-code&state=oauth-state",
3002+
);
3003+
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
29993004

30003005
const { runCodexMultiAuthCli } = await import("../lib/codex-manager.js");
30013006
const exitCode = await runCodexMultiAuthCli(["auth", "login", "--manual"]);
3007+
const renderedLogs = logSpy.mock.calls.flat().map((entry) => String(entry));
30023008

30033009
expect(exitCode).toBe(0);
30043010
expect(openBrowserUrlMock).not.toHaveBeenCalled();
3011+
expect(waitForCodeMock).not.toHaveBeenCalled();
3012+
expect(renderedLogs.some((entry) => entry.includes("Callback listener unavailable"))).toBe(
3013+
true,
3014+
);
3015+
expect(renderedLogs.some((entry) => entry.includes("No callback received"))).toBe(false);
30053016
expect(storageState.accounts).toHaveLength(1);
30063017
});
30073018

@@ -3105,6 +3116,48 @@ describe("codex manager cli commands", () => {
31053116
expect(storageState.accounts).toHaveLength(1);
31063117
});
31073118

3119+
it("rejects mismatched manual callback state in non-tty mode without persisting login", async () => {
3120+
setInteractiveTTY(false);
3121+
let storageState = {
3122+
version: 3 as const,
3123+
activeIndex: 0,
3124+
activeIndexByFamily: { codex: 0 },
3125+
accounts: [] as Array<Record<string, unknown>>,
3126+
};
3127+
loadAccountsMock.mockImplementation(async () => structuredClone(storageState));
3128+
saveAccountsMock.mockImplementation(async (nextStorage) => {
3129+
storageState = structuredClone(nextStorage);
3130+
});
3131+
promptLoginModeMock.mockResolvedValueOnce({ mode: "cancel" });
3132+
promptQuestionMock.mockResolvedValueOnce(
3133+
"http://127.0.0.1:1455/auth/callback?code=oauth-code&state=wrong-state",
3134+
);
3135+
3136+
const authModule = await import("../lib/auth/auth.js");
3137+
vi.mocked(authModule.createAuthorizationFlow).mockResolvedValueOnce({
3138+
pkce: { challenge: "pkce-challenge", verifier: "pkce-verifier" },
3139+
state: "oauth-state",
3140+
url: "https://auth.openai.com/mock",
3141+
});
3142+
const exchangeAuthorizationCodeMock = vi.mocked(authModule.exchangeAuthorizationCode);
3143+
3144+
const browserModule = await import("../lib/auth/browser.js");
3145+
const openBrowserUrlMock = vi.mocked(browserModule.openBrowserUrl);
3146+
const serverModule = await import("../lib/auth/server.js");
3147+
vi.mocked(serverModule.startLocalOAuthServer).mockRejectedValueOnce(
3148+
new Error("port in use"),
3149+
);
3150+
3151+
const { runCodexMultiAuthCli } = await import("../lib/codex-manager.js");
3152+
const exitCode = await runCodexMultiAuthCli(["auth", "login", "--manual"]);
3153+
3154+
expect(exitCode).toBe(0);
3155+
expect(promptQuestionMock).toHaveBeenCalledWith("");
3156+
expect(openBrowserUrlMock).not.toHaveBeenCalled();
3157+
expect(exchangeAuthorizationCodeMock).not.toHaveBeenCalled();
3158+
expect(storageState.accounts).toHaveLength(0);
3159+
});
3160+
31083161
it("falls back to pasted manual input when Windows-style callback bind fails", async () => {
31093162
setInteractiveTTY(false);
31103163
const now = Date.now();

0 commit comments

Comments
 (0)