From 383814366bc2843a4af74408b54656ec8453e5de Mon Sep 17 00:00:00 2001 From: t Date: Fri, 10 Jul 2026 15:22:12 -0700 Subject: [PATCH] fix(browse): closeTab loses last-tab auto-create when 'close' event wins the race The page 'close' event handler can run while page.close() is awaited and reassign activeTabId before closeTab's post-close active check, so the last-tab auto-create branch never ran and the manager was left with zero tabs. Capture wasActive before close(); only reassign activeTabId when it no longer points at a live tab. Event-dispatch order varies across Playwright/Chromium versions, which is why this raced on some machines only. Co-Authored-By: Claude Fable 5 --- browse/src/browser-manager.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/browse/src/browser-manager.ts b/browse/src/browser-manager.ts index f9f3317b5..21df4feb3 100644 --- a/browse/src/browser-manager.ts +++ b/browse/src/browser-manager.ts @@ -798,19 +798,31 @@ export class BrowserManager { const page = this.pages.get(tabId); if (!page) throw new Error(`Tab ${tabId} not found`); + // Capture BEFORE close(): the page 'close' event handler wired in + // wirePageEvents() can fire while page.close() is awaited. It removes + // the tab from the maps and reassigns activeTabId (to 0 when no tabs + // remain), so a post-close `tabId === this.activeTabId` check is + // order-dependent — whether the event dispatches before or after + // close() resolves varies across Playwright/Chromium versions and + // machines, and losing the race means the last-tab auto-create below + // never runs, leaving the manager with zero tabs. + const wasActive = tabId === this.activeTabId; + await page.close(); this.pages.delete(tabId); this.tabSessions.delete(tabId); this.tabOwnership.delete(tabId); // Switch to another tab if we closed the active one - if (tabId === this.activeTabId) { + if (wasActive) { const remaining = [...this.pages.keys()]; - if (remaining.length > 0) { - this.activeTabId = remaining[remaining.length - 1]; - } else { + if (remaining.length === 0) { // No tabs left — create a new blank one await this.newTab(); + } else if (!this.pages.has(this.activeTabId)) { + // The 'close' handler may have already switched to a valid tab; + // only reassign when activeTabId no longer points at a live tab. + this.activeTabId = remaining[remaining.length - 1]; } } }