fix(codex): suppress false restart notices after reauth

This commit is contained in:
Jinwoo-H
2026-08-21 21:22:23 -04:00
parent a40d118560
commit 4c484f0918
2 changed files with 91 additions and 16 deletions
@@ -668,16 +668,90 @@ describe('markLiveCodexSessionsForRestart lane scoping', () => {
vi.mocked(window.api.codexAccounts.listRecordedPaneLanes).mockResolvedValue({
'pty-1': 'host'
})
vi.mocked(window.api.codexAccounts.listStalePanes).mockResolvedValue([
{
ptyId: 'pty-1',
launchAccountId: 'account-a',
activeAccountId: 'account-b',
reason: 'account-change'
}
])
await markLiveCodexSessionsForRestart({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
previousAccountId: 'account-a',
nextAccountId: 'account-b',
target: { runtime: 'host' }
})
expect(useAppStore.getState().codexRestartNoticeByPtyId['pty-1']).toEqual({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B
nextAccountLabel: ACCOUNT_B,
previousAccountId: 'account-a',
nextAccountId: 'account-b'
})
})
it('suppresses a null-to-account reauth notice when the recorded pane is already current', async () => {
seedPanes([{ ptyId: 'pty-1' }])
vi.mocked(window.api.codexAccounts.listRecordedPaneLanes).mockResolvedValue({
'pty-1': 'host'
})
await markLiveCodexSessionsForRestart({
previousAccountLabel: 'System default',
nextAccountLabel: ACCOUNT_A,
previousAccountId: null,
nextAccountId: 'account-a',
target: { runtime: 'host' }
})
expect(window.api.codexAccounts.listStalePanes).toHaveBeenCalledWith({
ptyIds: ['pty-1']
})
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({})
})
it('classifies current, stale, and unrecorded panes independently', async () => {
seedPanes([{ ptyId: 'pty-current' }, { ptyId: 'pty-stale' }, { ptyId: 'pty-unknown' }])
vi.mocked(window.api.codexAccounts.listRecordedPaneLanes).mockResolvedValue({
'pty-current': 'host',
'pty-stale': 'host'
})
vi.mocked(window.api.codexAccounts.listStalePanes).mockResolvedValue([
{
ptyId: 'pty-stale',
launchAccountId: 'account-a',
activeAccountId: 'account-b',
reason: 'account-change'
}
])
await markLiveCodexSessionsForRestart({
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
previousAccountId: 'account-a',
nextAccountId: 'account-b',
target: { runtime: 'host' }
})
expect(window.api.codexAccounts.listStalePanes).toHaveBeenCalledWith({
ptyIds: ['pty-current', 'pty-stale']
})
expect(useAppStore.getState().codexRestartNoticeByPtyId).toEqual({
'pty-stale': {
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
previousAccountId: 'account-a',
nextAccountId: 'account-b'
},
'pty-unknown': {
previousAccountLabel: ACCOUNT_A,
nextAccountLabel: ACCOUNT_B,
previousAccountId: 'account-a',
nextAccountId: 'account-b'
}
})
})
+16 -15
View File
@@ -214,38 +214,39 @@ export async function markLiveCodexSessionsForRestart(args: {
return
}
const currentState = useAppStore.getState()
const restoredRouteNoticePtyIds = liveCodexSessionPtyIds.filter(
(ptyId) => currentState.codexRestartNoticeByPtyId[ptyId]?.homeRouteChanged === true
)
const restoredRouteNoticePtyIdSet = new Set(restoredRouteNoticePtyIds)
const recordedLiveScans = scans.filter((scan) => scan.eligible && scan.laneSource === 'recorded')
// Why: a reauth can report null -> A even though a pane's immutable launch
// route was already A; main's per-PTY record is the restart authority.
const authoritativeStalePanes =
restoredRouteNoticePtyIds.length === 0
recordedLiveScans.length === 0
? null
: await window.api.codexAccounts
.listStalePanes({ ptyIds: restoredRouteNoticePtyIds })
.listStalePanes({ ptyIds: recordedLiveScans.map((scan) => scan.ptyId) })
.catch(() => null)
const authoritativeStaleByPtyId = authoritativeStalePanes
? new Map(authoritativeStalePanes.map((pane) => [pane.ptyId, pane]))
: null
if (authoritativeStaleByPtyId) {
for (const ptyId of restoredRouteNoticePtyIds) {
if (!authoritativeStaleByPtyId.has(ptyId)) {
useAppStore.getState().clearCodexRestartNotice(ptyId)
for (const scan of recordedLiveScans) {
if (!authoritativeStaleByPtyId.has(scan.ptyId)) {
useAppStore.getState().clearCodexRestartNotice(scan.ptyId)
}
}
}
useAppStore.getState().markCodexRestartNotices(
liveCodexSessionPtyIds.flatMap((ptyId) => {
if (authoritativeStaleByPtyId && restoredRouteNoticePtyIdSet.has(ptyId)) {
const stalePane = authoritativeStaleByPtyId.get(ptyId)
scans.flatMap((scan) => {
if (!scan.eligible) {
return []
}
if (authoritativeStaleByPtyId && scan.laneSource === 'recorded') {
const stalePane = authoritativeStaleByPtyId.get(scan.ptyId)
if (!stalePane) {
return []
}
return [
{
ptyId,
ptyId: scan.ptyId,
previousAccountLabel: args.previousAccountLabel,
nextAccountLabel: args.nextAccountLabel,
previousAccountId: stalePane.launchAccountId,
@@ -256,7 +257,7 @@ export async function markLiveCodexSessionsForRestart(args: {
}
return [
{
ptyId,
ptyId: scan.ptyId,
previousAccountLabel: args.previousAccountLabel,
nextAccountLabel: args.nextAccountLabel,
...(args.previousAccountId === undefined