fix(grok): prefer auth.x.ai session and mention Grok in OSC52 toast (#8392)

* fix(grok): prefer auth.x.ai session and mention Grok in OSC52 toast

auth.json can hold multiple issuer keys; Object.values order could pick a
stale entry before the live auth.x.ai OIDC session. Prefer auth.x.ai keys
and fall back only when none exist. Also list Grok in the OSC52 blocked
clipboard toast so Grok TUI copy failures point at the same setting.

* fix(grok): harden preferred auth session selection

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
BingZ
2026-07-12 21:45:36 -07:00
committed by GitHub
co-authored by Orca Jinwoo-H
parent a67cbf2d24
commit 81ed5734d6
9 changed files with 173 additions and 20 deletions
+108
View File
@@ -45,4 +45,112 @@ describe('readGrokAuthSession', () => {
error: 'Grok auth file is invalid'
})
})
it.each(['https://auth.x.ai', 'https://auth.x.ai::client'])(
'prefers the %s issuer entry over an earlier alternate issuer',
async (preferredIssuer) => {
vi.doMock('node:fs', () => ({
existsSync: vi.fn(() => true),
readFileSync: vi.fn(() =>
JSON.stringify({
'https://stale.example.com::client': {
key: 'stale-token',
user_id: 'stale-user',
email: 'stale@example.com',
expires_at: '2099-01-01T00:00:00.000Z'
},
[preferredIssuer]: {
key: 'live-token',
user_id: 'live-user',
email: 'live@example.com',
team_id: 'team-1',
expires_at: '2099-06-01T00:00:00.000Z',
oidc_client_id: 'client-1'
}
})
)
}))
const { readGrokAuthSession } = await import('./grok-auth')
expect(readGrokAuthSession()).toEqual({
status: 'ok',
session: {
accessToken: 'live-token',
userId: 'live-user',
email: 'live@example.com',
teamId: 'team-1',
expiresAtMs: Date.parse('2099-06-01T00:00:00.000Z'),
oidcClientId: 'client-1'
}
})
}
)
it('falls back to the first tokenized entry when no auth.x.ai key exists', async () => {
vi.doMock('node:fs', () => ({
existsSync: vi.fn(() => true),
readFileSync: vi.fn(() =>
JSON.stringify({
'https://alternate.example.com::client': {
key: 'alt-token',
user_id: 'alt-user',
email: 'alt@example.com',
expires_at: '2099-01-01T00:00:00.000Z'
}
})
)
}))
const { readGrokAuthSession } = await import('./grok-auth')
expect(readGrokAuthSession()).toEqual({
status: 'ok',
session: {
accessToken: 'alt-token',
userId: 'alt-user',
email: 'alt@example.com',
teamId: null,
expiresAtMs: Date.parse('2099-01-01T00:00:00.000Z'),
oidcClientId: null
}
})
})
it('skips an expired auth.x.ai client entry when a fresh one follows it', async () => {
vi.doMock('node:fs', () => ({
existsSync: vi.fn(() => true),
readFileSync: vi.fn(() =>
JSON.stringify({
'https://auth.x.ai::old-client': {
key: 'expired-token',
expires_at: '2020-01-01T00:00:00.000Z'
},
'https://auth.x.ai::current-client': {
key: 'fresh-token',
expires_at: '2099-01-01T00:00:00.000Z'
}
})
)
}))
const { readGrokAuthSession } = await import('./grok-auth')
expect(readGrokAuthSession()).toMatchObject({
status: 'ok',
session: { accessToken: 'fresh-token' }
})
})
it('does not resurrect an alternate issuer when an auth.x.ai entry is tokenless', async () => {
vi.doMock('node:fs', () => ({
existsSync: vi.fn(() => true),
readFileSync: vi.fn(() =>
JSON.stringify({
'https://alternate.example.com::client': { key: 'stale-token' },
'https://auth.x.ai::client': { user_id: 'signed-out-user' }
})
)
}))
const { readGrokAuthSession } = await import('./grok-auth')
expect(readGrokAuthSession()).toEqual({ status: 'missing' })
})
})
+43 -14
View File
@@ -29,6 +29,8 @@ type GrokAuthEntry = {
oidc_client_id?: string
}
type TokenizedGrokAuthEntry = GrokAuthEntry & { key: string }
export type GrokAuthReadResult =
| { status: 'missing' }
| { status: 'error'; error: string }
@@ -43,7 +45,7 @@ function getGrokAuthReadError(err: unknown): string {
return 'Unable to read Grok auth file'
}
function parseAuthEntry(value: unknown): GrokAuthEntry | null {
function parseAuthEntry(value: unknown): TokenizedGrokAuthEntry | null {
if (typeof value !== 'object' || value === null) {
return null
}
@@ -51,7 +53,7 @@ function parseAuthEntry(value: unknown): GrokAuthEntry | null {
if (typeof entry.key !== 'string' || entry.key.length === 0) {
return null
}
return entry
return entry as TokenizedGrokAuthEntry
}
function parseExpiresAtMs(iso: string | undefined): number | null {
@@ -62,6 +64,24 @@ function parseExpiresAtMs(iso: string | undefined): number | null {
return Number.isFinite(ms) ? ms : null
}
// Why: stale alternate issuers can precede the default xAI OAuth session in auth.json.
const PREFERRED_GROK_AUTH_ISSUER = 'https://auth.x.ai'
function sessionFromAuthEntry(authEntry: TokenizedGrokAuthEntry): GrokAuthSession {
return {
accessToken: authEntry.key,
userId: typeof authEntry.user_id === 'string' ? authEntry.user_id : null,
email: typeof authEntry.email === 'string' ? authEntry.email : null,
teamId: typeof authEntry.team_id === 'string' ? authEntry.team_id : null,
expiresAtMs: parseExpiresAtMs(authEntry.expires_at),
oidcClientId: typeof authEntry.oidc_client_id === 'string' ? authEntry.oidc_client_id : null
}
}
function isPreferredGrokAuthKey(key: string): boolean {
return key === PREFERRED_GROK_AUTH_ISSUER || key.startsWith(`${PREFERRED_GROK_AUTH_ISSUER}::`)
}
export function readGrokAuthSession(): GrokAuthReadResult {
const path = getGrokAuthPath()
if (!existsSync(path)) {
@@ -72,23 +92,32 @@ export function readGrokAuthSession(): GrokAuthReadResult {
if (typeof parsed !== 'object' || parsed === null) {
return { status: 'error', error: 'Grok auth file is invalid' }
}
for (const entry of Object.values(parsed)) {
let preferredKeySeen = false
let expiredPreferred: GrokAuthSession | null = null
let fallback: GrokAuthSession | null = null
for (const [key, entry] of Object.entries(parsed as Record<string, unknown>)) {
const isPreferred = isPreferredGrokAuthKey(key)
preferredKeySeen ||= isPreferred
const authEntry = parseAuthEntry(entry)
if (!authEntry?.key) {
if (!authEntry) {
continue
}
return {
status: 'ok',
session: {
accessToken: authEntry.key,
userId: typeof authEntry.user_id === 'string' ? authEntry.user_id : null,
email: typeof authEntry.email === 'string' ? authEntry.email : null,
teamId: typeof authEntry.team_id === 'string' ? authEntry.team_id : null,
expiresAtMs: parseExpiresAtMs(authEntry.expires_at),
oidcClientId:
typeof authEntry.oidc_client_id === 'string' ? authEntry.oidc_client_id : null
const session = sessionFromAuthEntry(authEntry)
if (isPreferred) {
if (isGrokAccessTokenFresh(session)) {
return { status: 'ok', session }
}
expiredPreferred ??= session
continue
}
if (!fallback) {
fallback = session
}
}
// Why: alternate issuers are compatibility fallbacks only when no default entry exists.
const selectedSession = expiredPreferred ?? (preferredKeySeen ? null : fallback)
if (selectedSession) {
return { status: 'ok', session: selectedSession }
}
// Why: a token-less file (e.g. after grok logout) means signed out, not a
// failure — 'error' would keep a status-bar alert visible for that user.
@@ -1,4 +1,9 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import en from '@/i18n/locales/en.json'
import es from '@/i18n/locales/es.json'
import ja from '@/i18n/locales/ja.json'
import ko from '@/i18n/locales/ko.json'
import zh from '@/i18n/locales/zh.json'
import { OSC52_CLIPBOARD_SETTING_ID } from './osc52-clipboard-setting-anchor'
import type * as Osc52ClipboardBlockedToastModule from './osc52-clipboard-blocked-toast'
@@ -41,6 +46,7 @@ describe('showOsc52ClipboardBlockedToast', () => {
showOsc52ClipboardBlockedToast()
expect(toastInfoMock.mock.calls[0]?.[1]?.description).toContain('Grok')
const options = toastInfoMock.mock.calls[0]?.[1]
expect(options).toMatchObject({
action: {
@@ -67,4 +73,14 @@ describe('showOsc52ClipboardBlockedToast', () => {
expect(toastInfoMock).toHaveBeenCalledTimes(1)
})
it('mentions Grok in every supported locale', () => {
const locales = [en, es, ja, ko, zh]
for (const locale of locales) {
expect(
locale.auto.components.terminal.pane.osc52.clipboard.blocked.toast['7cf51f74fd']
).toContain('Grok')
}
})
})
@@ -19,7 +19,7 @@ export function showOsc52ClipboardBlockedToast(): void {
{
description: translate(
'auto.components.terminal.pane.osc52.clipboard.blocked.toast.7cf51f74fd',
'Enable TUI clipboard writes in Terminal settings to copy from SSH, tmux, Neovim, or fzf.'
'Enable TUI clipboard writes in Terminal settings to copy from SSH, tmux, Neovim, fzf, or Grok.'
),
duration: 12_000,
action: {
+1 -1
View File
@@ -2588,7 +2588,7 @@
"blocked": {
"toast": {
"97c98f1afe": "Open Setting",
"7cf51f74fd": "Enable TUI clipboard writes in Terminal settings to copy from SSH, tmux, Neovim, or fzf.",
"7cf51f74fd": "Enable TUI clipboard writes in Terminal settings to copy from SSH, tmux, Neovim, fzf, or Grok.",
"89eaa3e80b": "Terminal clipboard write blocked"
}
}
+1 -1
View File
@@ -2588,7 +2588,7 @@
"blocked": {
"toast": {
"97c98f1afe": "Abrir ajustes",
"7cf51f74fd": "Habilita las escrituras al portapapeles TUI en los ajustes de Terminal para copiar desde SSH, tmux, Neovim o fzf.",
"7cf51f74fd": "Habilita las escrituras al portapapeles TUI en los ajustes de Terminal para copiar desde SSH, tmux, Neovim, fzf o Grok.",
"89eaa3e80b": "Escritura al portapapeles del terminal bloqueada"
}
}
+1 -1
View File
@@ -2588,7 +2588,7 @@
"blocked": {
"toast": {
"97c98f1afe": "設定を開く",
"7cf51f74fd": "SSH、tmux、Neovim、または fzf からコピーするには、Terminal 設定で TUI クリップボードへの書き込みを有効にします。",
"7cf51f74fd": "SSH、tmux、Neovim、fzf、または Grok からコピーするには、Terminal 設定で TUI クリップボードへの書き込みを有効にします。",
"89eaa3e80b": "Terminal のクリップボードへの書き込みがブロックされました"
}
}
+1 -1
View File
@@ -2588,7 +2588,7 @@
"blocked": {
"toast": {
"97c98f1afe": "설정 열기",
"7cf51f74fd": "SSH, tmux, Neovim 또는 fzf에서 복사하려면 Terminal 설정에서 TUI 클립보드 쓰기를 활성화하세요.",
"7cf51f74fd": "SSH, tmux, Neovim, fzf 또는 Grok에서 복사하려면 Terminal 설정에서 TUI 클립보드 쓰기를 활성화하세요.",
"89eaa3e80b": "Terminal 클립보드 쓰기가 차단되었습니다."
}
}
+1 -1
View File
@@ -2588,7 +2588,7 @@
"blocked": {
"toast": {
"97c98f1afe": "打开设置",
"7cf51f74fd": "在终端设置中启用 TUI 剪贴板写入,以从 SSH、tmux、Neovim 或 fzf 进行复制。",
"7cf51f74fd": "在终端设置中启用 TUI 剪贴板写入,以从 SSH、tmux、Neovim、fzf 或 Grok 进行复制。",
"89eaa3e80b": "终端剪贴板写入被阻止"
}
}