From f5ab9bd112ab786c466c63293d2bfd92523eaa79 Mon Sep 17 00:00:00 2001 From: Samuel Mota Date: Fri, 9 Oct 2026 09:34:36 +0200 Subject: [PATCH 1/3] fix(provider): always display authorization URL in terminal --- src/provider/accounts.ts | 7 +++++-- tests/provider/accounts.test.ts | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/provider/accounts.ts b/src/provider/accounts.ts index 52533d4..6e80b3e 100644 --- a/src/provider/accounts.ts +++ b/src/provider/accounts.ts @@ -134,10 +134,13 @@ function terminalInteraction(ctx: ExtensionCommandContext): AuthInteraction { return { notify(event) { if (event.type === 'auth_url') { + ctx.ui.notify(`Open this URL to continue login: ${event.url}`, 'info'); openAuthorizationUrl(event.url, () => { - ctx.ui.notify(`Open this URL to continue login: ${event.url}`, 'warning'); + ctx.ui.notify(`Could not open browser automatically: ${event.url}`, 'warning'); }); - ctx.ui.notify(event.instructions ?? 'Complete the Grok CLI login in your browser.', 'info'); + if (event.instructions) { + ctx.ui.notify(event.instructions, 'info'); + } return; } if (event.type === 'device_code') { diff --git a/tests/provider/accounts.test.ts b/tests/provider/accounts.test.ts index 917d894..a79e956 100644 --- a/tests/provider/accounts.test.ts +++ b/tests/provider/accounts.test.ts @@ -413,6 +413,10 @@ describe('vault account management', () => { await commands.get('grok-cli-accounts')?.handler('', commandContext); + expect(commandContext.ui.notify).toHaveBeenCalledWith( + expect.stringContaining('https://accounts.x.ai/authorize?state=test'), + 'info', + ); expect(commandContext.ui.notify).toHaveBeenCalledWith( expect.stringContaining('https://accounts.x.ai/authorize?state=test'), 'warning', From dcdafc38e39e0dc7195fd11c977b33814b083283 Mon Sep 17 00:00:00 2001 From: Samuel Mota Date: Fri, 9 Oct 2026 11:13:44 +0200 Subject: [PATCH 2/3] fix(provider): report authorization URL on non-zero opener exit code --- src/provider/accounts.ts | 18 ++++++++++++------ tests/provider/accounts.test.ts | 17 +++++++++++------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/provider/accounts.ts b/src/provider/accounts.ts index 6e80b3e..dd2447f 100644 --- a/src/provider/accounts.ts +++ b/src/provider/accounts.ts @@ -126,7 +126,16 @@ function openAuthorizationUrl(url: string, onError: () => void) { ? ['rundll32', ['url.dll,FileProtocolHandler', url]] : ['xdg-open', [url]]; const child = spawn(command[0], command[1], { detached: true, stdio: 'ignore' }); - child.once('error', onError); + let failed = false; + const fail = () => { + if (failed) return; + failed = true; + onError(); + }; + child.once('error', fail); + child.once('exit', (code) => { + if (code !== 0 && code !== null) fail(); + }); child.unref(); } @@ -134,13 +143,10 @@ function terminalInteraction(ctx: ExtensionCommandContext): AuthInteraction { return { notify(event) { if (event.type === 'auth_url') { - ctx.ui.notify(`Open this URL to continue login: ${event.url}`, 'info'); openAuthorizationUrl(event.url, () => { - ctx.ui.notify(`Could not open browser automatically: ${event.url}`, 'warning'); + ctx.ui.notify(`Open this URL to continue login: ${event.url}`, 'warning'); }); - if (event.instructions) { - ctx.ui.notify(event.instructions, 'info'); - } + ctx.ui.notify(event.instructions ?? 'Complete the Grok CLI login in your browser.', 'info'); return; } if (event.type === 'device_code') { diff --git a/tests/provider/accounts.test.ts b/tests/provider/accounts.test.ts index a79e956..fa6548d 100644 --- a/tests/provider/accounts.test.ts +++ b/tests/provider/accounts.test.ts @@ -379,12 +379,21 @@ describe('vault account management', () => { await expect(accounts.activate(ctx, account.id)).rejects.toThrow('environment token'); }); - it('reports the authorization URL when the browser opener fails', async () => { + it.each([ + { + failure: 'emits an error', + trigger: (child: EventEmitter) => child.emit('error', new Error('missing')), + }, + { + failure: 'exits with non-zero code', + trigger: (child: EventEmitter) => child.emit('exit', 1), + }, + ])('reports the authorization URL when the browser opener $failure', async ({ trigger }) => { const child = Object.assign(new EventEmitter(), { unref: vi.fn() }); spawnProcess.mockReturnValue(child); login.mockImplementationOnce(async (callbacks) => { callbacks.onAuth({ url: 'https://accounts.x.ai/authorize?state=test' }); - child.emit('error', new Error('xdg-open missing')); + trigger(child); throw new Error('stop test login'); }); const commands = new Map< @@ -413,10 +422,6 @@ describe('vault account management', () => { await commands.get('grok-cli-accounts')?.handler('', commandContext); - expect(commandContext.ui.notify).toHaveBeenCalledWith( - expect.stringContaining('https://accounts.x.ai/authorize?state=test'), - 'info', - ); expect(commandContext.ui.notify).toHaveBeenCalledWith( expect.stringContaining('https://accounts.x.ai/authorize?state=test'), 'warning', From edb123579ff4c00e5493046c177df50f9d9ed587 Mon Sep 17 00:00:00 2001 From: Samuel Mota Date: Fri, 9 Oct 2026 11:26:20 +0200 Subject: [PATCH 3/3] fix(provider): send authorization URL as warning to prevent TUI line overwrite --- src/provider/accounts.ts | 18 ++++-------------- tests/provider/accounts.test.ts | 19 ++++++++----------- 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/provider/accounts.ts b/src/provider/accounts.ts index dd2447f..fedbfb8 100644 --- a/src/provider/accounts.ts +++ b/src/provider/accounts.ts @@ -118,7 +118,7 @@ function callbacks(interaction: AuthInteraction): OAuthLoginCallbacks { }; } -function openAuthorizationUrl(url: string, onError: () => void) { +function openAuthorizationUrl(url: string) { const command: [string, string[]] = process.platform === 'darwin' ? ['open', [url]] @@ -126,16 +126,7 @@ function openAuthorizationUrl(url: string, onError: () => void) { ? ['rundll32', ['url.dll,FileProtocolHandler', url]] : ['xdg-open', [url]]; const child = spawn(command[0], command[1], { detached: true, stdio: 'ignore' }); - let failed = false; - const fail = () => { - if (failed) return; - failed = true; - onError(); - }; - child.once('error', fail); - child.once('exit', (code) => { - if (code !== 0 && code !== null) fail(); - }); + child.once('error', () => {}); child.unref(); } @@ -143,9 +134,8 @@ function terminalInteraction(ctx: ExtensionCommandContext): AuthInteraction { return { notify(event) { if (event.type === 'auth_url') { - openAuthorizationUrl(event.url, () => { - ctx.ui.notify(`Open this URL to continue login: ${event.url}`, 'warning'); - }); + openAuthorizationUrl(event.url); + ctx.ui.notify(`Open this URL to continue login: ${event.url}`, 'warning'); ctx.ui.notify(event.instructions ?? 'Complete the Grok CLI login in your browser.', 'info'); return; } diff --git a/tests/provider/accounts.test.ts b/tests/provider/accounts.test.ts index fa6548d..655edfe 100644 --- a/tests/provider/accounts.test.ts +++ b/tests/provider/accounts.test.ts @@ -379,21 +379,11 @@ describe('vault account management', () => { await expect(accounts.activate(ctx, account.id)).rejects.toThrow('environment token'); }); - it.each([ - { - failure: 'emits an error', - trigger: (child: EventEmitter) => child.emit('error', new Error('missing')), - }, - { - failure: 'exits with non-zero code', - trigger: (child: EventEmitter) => child.emit('exit', 1), - }, - ])('reports the authorization URL when the browser opener $failure', async ({ trigger }) => { + it('reports the authorization URL as a warning in terminal and launches browser opener', async () => { const child = Object.assign(new EventEmitter(), { unref: vi.fn() }); spawnProcess.mockReturnValue(child); login.mockImplementationOnce(async (callbacks) => { callbacks.onAuth({ url: 'https://accounts.x.ai/authorize?state=test' }); - trigger(child); throw new Error('stop test login'); }); const commands = new Map< @@ -422,10 +412,17 @@ describe('vault account management', () => { await commands.get('grok-cli-accounts')?.handler('', commandContext); + expect(spawnProcess).toHaveBeenCalled(); + expect(child.unref).toHaveBeenCalled(); + expect(() => child.emit('error', new Error('spawn failed'))).not.toThrow(); expect(commandContext.ui.notify).toHaveBeenCalledWith( expect.stringContaining('https://accounts.x.ai/authorize?state=test'), 'warning', ); + expect(commandContext.ui.notify).toHaveBeenCalledWith( + 'Complete the Grok CLI login in your browser.', + 'info', + ); }); it('does not save quota after the captured account logs out', async () => {