mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 08:03:28 +00:00
fix(codex): keep an unreadable managed home from dropping another account's mirrored refresh
An account whose auth.json cannot be read aborted the whole read-back scan, so removing the runtime mirror for an unproven selection destroyed a token refresh it held for a different account. Skip the unreadable home instead.
This commit is contained in:
@@ -543,68 +543,82 @@ describe('CodexRuntimeHomeService', () => {
|
||||
}
|
||||
)
|
||||
|
||||
it('preserves the mirrored refresh of the account the runtime home actually holds', async () => {
|
||||
const runtimeAuthPath = getRuntimeCodexAuthPath()
|
||||
writeFileSync(
|
||||
getSystemCodexAuthPath(),
|
||||
createCodexAuthJson('system@example.com', 'acct-system', 'system'),
|
||||
'utf-8'
|
||||
)
|
||||
const authA = createCodexAuthJson('a@example.com', 'acct-a', 'a', 1)
|
||||
const authARefreshed = createCodexAuthJson('a@example.com', 'acct-a', 'a-refreshed', 2)
|
||||
const homeA = createManagedAuth(testState.userDataDir, 'account-a', authA)
|
||||
const homeB = createManagedAuth(
|
||||
testState.userDataDir,
|
||||
'account-b',
|
||||
createCodexAuthJson('b@example.com', 'acct-b', 'b')
|
||||
)
|
||||
// A crash left account A's refreshed tokens in the mirror while settings
|
||||
// already select account B, whose auth.json was never written.
|
||||
writeFileSync(runtimeAuthPath, authARefreshed, 'utf-8')
|
||||
writeFileSync(
|
||||
getSharedRuntimeAuthProvenancePath(),
|
||||
`${JSON.stringify({ owner: 'managed', accountId: 'account-a' })}\n`,
|
||||
'utf-8'
|
||||
)
|
||||
rmSync(join(homeB, 'auth.json'), { force: true })
|
||||
const store = createStore(
|
||||
createSettings({
|
||||
codexManagedAccounts: [
|
||||
{
|
||||
id: 'account-a',
|
||||
email: 'a@example.com',
|
||||
managedHomePath: homeA,
|
||||
providerAccountId: 'acct-a',
|
||||
workspaceLabel: null,
|
||||
workspaceAccountId: 'acct-a',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
lastAuthenticatedAt: 1
|
||||
},
|
||||
{
|
||||
id: 'account-b',
|
||||
email: 'b@example.com',
|
||||
managedHomePath: homeB,
|
||||
providerAccountId: 'acct-b',
|
||||
workspaceLabel: null,
|
||||
workspaceAccountId: 'acct-b',
|
||||
createdAt: 2,
|
||||
updatedAt: 2,
|
||||
lastAuthenticatedAt: 2
|
||||
}
|
||||
],
|
||||
activeCodexManagedAccountId: 'account-b',
|
||||
activeCodexManagedAccountIdsByRuntime: { host: 'account-b', wsl: {} }
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
||||
new CodexRuntimeHomeService(store as never)
|
||||
it.each([
|
||||
['missing', (authPath: string) => rmSync(authPath, { force: true })],
|
||||
[
|
||||
'unreadable',
|
||||
(authPath: string) => {
|
||||
chmodSync(authPath, 0o000)
|
||||
}
|
||||
]
|
||||
])(
|
||||
'preserves the mirrored refresh of the account the runtime home actually holds when the selected account auth is %s',
|
||||
async (label, breakAuth) => {
|
||||
if (label === 'unreadable' && (process.platform === 'win32' || process.getuid?.() === 0)) {
|
||||
return
|
||||
}
|
||||
const runtimeAuthPath = getRuntimeCodexAuthPath()
|
||||
writeFileSync(
|
||||
getSystemCodexAuthPath(),
|
||||
createCodexAuthJson('system@example.com', 'acct-system', 'system'),
|
||||
'utf-8'
|
||||
)
|
||||
const authA = createCodexAuthJson('a@example.com', 'acct-a', 'a', 1)
|
||||
const authARefreshed = createCodexAuthJson('a@example.com', 'acct-a', 'a-refreshed', 2)
|
||||
const homeA = createManagedAuth(testState.userDataDir, 'account-a', authA)
|
||||
const homeB = createManagedAuth(
|
||||
testState.userDataDir,
|
||||
'account-b',
|
||||
createCodexAuthJson('b@example.com', 'acct-b', 'b')
|
||||
)
|
||||
// A crash left account A's refreshed tokens in the mirror while settings
|
||||
// already select account B, whose own auth.json cannot be read.
|
||||
writeFileSync(runtimeAuthPath, authARefreshed, 'utf-8')
|
||||
writeFileSync(
|
||||
getSharedRuntimeAuthProvenancePath(),
|
||||
`${JSON.stringify({ owner: 'managed', accountId: 'account-a' })}\n`,
|
||||
'utf-8'
|
||||
)
|
||||
breakAuth(join(homeB, 'auth.json'))
|
||||
const store = createStore(
|
||||
createSettings({
|
||||
codexManagedAccounts: [
|
||||
{
|
||||
id: 'account-a',
|
||||
email: 'a@example.com',
|
||||
managedHomePath: homeA,
|
||||
providerAccountId: 'acct-a',
|
||||
workspaceLabel: null,
|
||||
workspaceAccountId: 'acct-a',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
lastAuthenticatedAt: 1
|
||||
},
|
||||
{
|
||||
id: 'account-b',
|
||||
email: 'b@example.com',
|
||||
managedHomePath: homeB,
|
||||
providerAccountId: 'acct-b',
|
||||
workspaceLabel: null,
|
||||
workspaceAccountId: 'acct-b',
|
||||
createdAt: 2,
|
||||
updatedAt: 2,
|
||||
lastAuthenticatedAt: 2
|
||||
}
|
||||
],
|
||||
activeCodexManagedAccountId: 'account-b',
|
||||
activeCodexManagedAccountIdsByRuntime: { host: 'account-b', wsl: {} }
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => {})
|
||||
const { CodexRuntimeHomeService } = await import('./runtime-home-service')
|
||||
new CodexRuntimeHomeService(store as never)
|
||||
|
||||
expect(existsSync(runtimeAuthPath)).toBe(false)
|
||||
expect(readFileSync(join(homeA, 'auth.json'), 'utf-8')).toBe(authARefreshed)
|
||||
expect(store.getSettings().activeCodexManagedAccountIdsByRuntime?.host).toBe('account-b')
|
||||
})
|
||||
expect(existsSync(runtimeAuthPath)).toBe(false)
|
||||
expect(readFileSync(join(homeA, 'auth.json'), 'utf-8')).toBe(authARefreshed)
|
||||
expect(store.getSettings().activeCodexManagedAccountIdsByRuntime?.host).toBe('account-b')
|
||||
}
|
||||
)
|
||||
|
||||
it('keeps the runtime auth through the grace window while the selected account rotates', async () => {
|
||||
const runtimeAuthPath = getRuntimeCodexAuthPath()
|
||||
|
||||
@@ -1281,7 +1281,15 @@ export class CodexRuntimeHomeService {
|
||||
if (!existsSync(managedAuthPath)) {
|
||||
continue
|
||||
}
|
||||
const managedAuthContents = readFileSync(managedAuthPath, 'utf-8')
|
||||
let managedAuthContents: string
|
||||
try {
|
||||
managedAuthContents = readFileSync(managedAuthPath, 'utf-8')
|
||||
} catch {
|
||||
// Why: an unreadable home can never be the match, but letting the read
|
||||
// throw abandons the scan for every other account — dropping a refresh
|
||||
// the runtime home holds for one of them.
|
||||
continue
|
||||
}
|
||||
if (codexAuthMatchesManagedAccount(runtimeAuthContents, account, managedAuthContents)) {
|
||||
matches.push({ account, managedAuthPath, managedAuthContents })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user