fix(codex): detach restart progress from cleanup

This commit is contained in:
Brennan Benson
2026-08-02 17:22:47 -07:00
parent 17e85008dd
commit 836e1b0a6c
2 changed files with 21 additions and 17 deletions
@@ -293,8 +293,8 @@ describe('codex detached pane restart executor', () => {
expect(useAppStore.getState().ptyIdsByTabId['tab-1']).toEqual([OLD_PTY])
expect(useAppStore.getState().pendingCodexPaneRestartIds).toEqual({ [OLD_PTY]: true })
pendingKill.resolve()
await restart
pendingKill.resolve()
} finally {
unregister()
}
@@ -332,8 +332,8 @@ describe('codex detached pane restart executor', () => {
expect(after.ptyIdsByTabId['tab-1']).toEqual(['wt1@@successor'])
expect(after.terminalLayoutsByTabId['tab-1']?.ptyIdsByLeafId?.[LEAF_ID]).toBe('wt1@@successor')
expect(awaitsCodexRestartAnswer(after.codexRestartNoticeByPtyId['wt1@@successor'])).toBe(true)
pendingKill.resolve()
await restart
pendingKill.resolve()
})
it('leaves a sleep-retained pending id alone so wake can migrate it', async () => {
@@ -396,7 +396,7 @@ describe('codex detached pane restart executor', () => {
launchAgent: 'codex'
})
expect(useAppStore.getState().suppressedPtyExitIds[OLD_PTY]).toBeUndefined()
pendingKill.resolve()
await restart
pendingKill.resolve()
})
})
@@ -166,7 +166,7 @@ async function executeDetachedCodexPaneRestart(
store.consumeSuppressedPtyExit(ptyId)
store.queueTabStartupCommand(located.tab.id, { ...CODEX_ACCOUNT_RESTART_STARTUP })
store.clearCodexRestartNotice(ptyId)
await killReplacedCodexPanePty(ptyId)
killReplacedCodexPanePty(ptyId)
return
}
const { worktreeId, tab, leafId } = located
@@ -214,12 +214,12 @@ async function executeDetachedCodexPaneRestart(
const store = useAppStore.getState()
if (!isLocatedCodexPaneCurrent(store, located, ptyId)) {
reopenCurrentCodexRestartPrompt(located, ptyId)
await window.api.pty.kill(spawned.id).catch(() => {})
reapUnboundCodexPty(spawned.id, 'stale detached spawn')
return
}
if (hasRegisteredRuntimeTerminalTab(tab.id) || ptyDataHandlers.has(ptyId)) {
store.queueCodexPaneRestarts([ptyId])
await window.api.pty.kill(spawned.id).catch(() => {})
reapUnboundCodexPty(spawned.id, 'mounted-owner handoff spawn')
return
}
store.updateTabPtyId(tab.id, spawned.id, ptyId)
@@ -227,7 +227,7 @@ async function executeDetachedCodexPaneRestart(
// Why: the tab was retired while the spawn was in flight; without a binding
// the fresh PTY would idle in the daemon forever, so reap it and stand down.
store.clearCodexRestartNotice(ptyId)
await window.api.pty.kill(spawned.id).catch(() => {})
reapUnboundCodexPty(spawned.id, 'retired-tab spawn')
return
}
rebindCodexPaneLayoutLeaf(tab.id, leafId, spawned.id)
@@ -236,7 +236,7 @@ async function executeDetachedCodexPaneRestart(
store.clearCodexRestartNotice(spawned.id)
store.clearCodexRestartNotice(ptyId)
await killReplacedCodexPanePty(ptyId)
killReplacedCodexPanePty(ptyId)
}
function isLocatedCodexPaneCurrent(
@@ -307,19 +307,23 @@ function rebindCodexPaneLayoutLeaf(tabId: string, leafId: string, newPtyId: stri
store.replaceTerminalLayoutPanePtyId(tabId, leafId, newPtyId)
}
async function killReplacedCodexPanePty(ptyId: string): Promise<void> {
function reapUnboundCodexPty(ptyId: string, reason: string): void {
try {
void window.api.pty.kill(ptyId).catch((err) => {
console.warn(`[codex-restart] failed to reap ${reason}:`, err)
})
} catch (err) {
console.warn(`[codex-restart] failed to reap ${reason}:`, err)
}
discardPreHandlerPtyState(ptyId)
}
function killReplacedCodexPanePty(ptyId: string): void {
// Why the disposal: a parked tab's exit sidecar treats any exit as the pane
// dying — it would collapse the just-rebound leaf or close the whole tab.
disposeParkedTerminalWatchersForPtyIds([ptyId])
for (const snapshot of unregisterPtyDataHandlers([ptyId])) {
snapshot.commit()
}
try {
await window.api.pty.kill(ptyId)
} catch (err) {
// Why not rethrow: the stale shell can only be reaped, not re-adopted, so a
// failed kill is log-worthy only — the restart itself already happened.
console.warn('[codex-restart] failed to kill replaced Codex pane PTY:', err)
}
discardPreHandlerPtyState(ptyId)
reapUnboundCodexPty(ptyId, 'replaced Codex pane PTY')
}