From f57941363113942cf25dede0d4e41e0deab40b43 Mon Sep 17 00:00:00 2001 From: Andrey Bragin Date: Thu, 8 Oct 2026 15:08:04 +0400 Subject: [PATCH] fix: report cancelled device-code sign-in as auth error instead of invalid params --- src/CodexAcpClient.ts | 5 +++- src/CodexAcpServer.ts | 2 +- .../CodexACPAgent/CodexAcpClient.test.ts | 29 +++++++++++++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/CodexAcpClient.ts b/src/CodexAcpClient.ts index d26177799..565c829a9 100644 --- a/src/CodexAcpClient.ts +++ b/src/CodexAcpClient.ts @@ -293,7 +293,10 @@ export class CodexAcpClient { if (!acp.CreateElicitationResponse.isAccept(first.response)) { await this.codexClient.accountLoginCancel({loginId: loginResponse.loginId}); - return false; + throw RequestError.requestCancelled( + {methodId: "chat-gpt-device-code", action: first.response.action}, + "ChatGPT device code sign-in was cancelled", + ); } const result = await loginCompletedPromise; diff --git a/src/CodexAcpServer.ts b/src/CodexAcpServer.ts index 63ae8011c..b7fd690ee 100644 --- a/src/CodexAcpServer.ts +++ b/src/CodexAcpServer.ts @@ -1226,7 +1226,7 @@ export class CodexAcpServer { const isAuthenticated = await this.runWithProcessCheck(() => this.codexAcpClient.authenticate(_params, elicitationRequester)); if (!isAuthenticated) { logger.log("Authenticate request failed"); - throw RequestError.invalidParams(); + throw RequestError.authRequired({methodId: _params.methodId}, "Sign-in did not complete"); } await this.refreshAuthState(this.getAuthProviderForAuthenticateRequest(_params)); logger.log("Authenticate request completed"); diff --git a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts index 78d6efe4f..7fd3ab720 100644 --- a/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts +++ b/src/__tests__/CodexACPAgent/CodexAcpClient.test.ts @@ -274,7 +274,7 @@ describe('ACP server test', { timeout: 40_000 }, () => { }); }); - it('should cancel ChatGPT device code login when URL elicitation is declined', async () => { + it.each(["decline", "cancel"] as const)('should cancel ChatGPT device code login when URL elicitation is answered with %s', async (action) => { const { deviceFixture, codexAppServerClient } = createDeviceCodeFixture(); const codexAcpAgent = deviceFixture.getCodexAcpAgent(); await codexAcpAgent.initialize({ @@ -283,16 +283,39 @@ describe('ACP server test', { timeout: 40_000 }, () => { }); const cancelSpy = vi.spyOn(codexAppServerClient, "accountLoginCancel") .mockResolvedValue({ status: "canceled" }); - deviceFixture.setElicitationResponse({ action: "decline" }); + deviceFixture.setElicitationResponse({ action }); await expect(codexAcpAgent.authenticate({ methodId: "chat-gpt-device-code" }, 42)) - .rejects.toThrow(); + .rejects.toMatchObject({ + code: -32800, + message: "Request cancelled: ChatGPT device code sign-in was cancelled", + data: { methodId: "chat-gpt-device-code", action }, + }); expect(cancelSpy).toHaveBeenCalledWith({ loginId: "login-1" }); expect(deviceFixture.getAcpConnectionEvents([]) .some(event => event.method === "completeElicitation")) .toBe(false); }); + it('should report an incomplete sign-in when ChatGPT device code login fails', async () => { + const { deviceFixture, completeLogin, loginCompletedSubscribed } = createDeviceCodeFixture(); + const codexAcpAgent = deviceFixture.getCodexAcpAgent(); + await codexAcpAgent.initialize({ + protocolVersion: 1, + clientCapabilities: { elicitation: { url: {} } }, + }); + deviceFixture.setElicitationResponse({ action: "accept" }); + + const authPromise = codexAcpAgent.authenticate({ methodId: "chat-gpt-device-code" }, 42); + await vi.waitFor(() => expect(loginCompletedSubscribed()).toBe(true)); + completeLogin(false); + await expect(authPromise).rejects.toMatchObject({ + code: -32000, + message: "Authentication required: Sign-in did not complete", + data: { methodId: "chat-gpt-device-code" }, + }); + }); + it('should complete URL elicitation when login finishes before the elicitation response', async () => { const { deviceFixture, completeLogin, loginCompletedSubscribed } = createDeviceCodeFixture(); const codexAcpAgent = deviceFixture.getCodexAcpAgent();