v1.43.3.0 fix(browse): headed-mode idle timer + onDisconnect target wrong BrowserManager for embedders#1645
Merged
Merged
Conversation
…indirection Module-level idleCheckTick, parent watchdog, SIGTERM handler, and buildFetchHandler's onDisconnect wire all read the module-level BrowserManager directly. For embedders (gbrowser) that pass their own instance into buildFetchHandler, the module-level instance never has launchHeaded() called on it — connectionMode stays 'launched' forever, headed-mode early-returns never fire, and after 30 min of HTTP idle the server self-terminates out from under the overlay. Adds `let activeBrowserManager: BrowserManager` at module scope (symmetric with the existing `let activeShutdown` pattern). buildFetchHandler retargets it at cfg.browserManager and CHAINS cfg.browserManager.onDisconnect to activeShutdown, preserving any caller-installed handler instead of clobbering it. Six edit sites in browse/src/server.ts: - Edit 1 (~705): declare activeBrowserManager - Edit 2 (~596): extract idleCheckTick + __testInternals__ export - Edit 3 (~658): parent watchdog reads activeBrowserManager - Edit 4 (~1387): retarget + chain cfgBrowserManager.onDisconnect - Edit 5 (verify): line 714 default stays in place - Edit 6 (~1212): SIGTERM handler reads activeBrowserManager
…rally Adds 5 behavioral tests to browse/test/server-factory.test.ts under a new 'idle timer + onDisconnect dual-instance fix' describe block: - T1 (CRITICAL — REGRESSION): headed embedder does not auto-shutdown at idle. Pins the bug this PR fixes. - T2 (paired defensive): headless still auto-shuts down at idle. Catches a future refactor that breaks the inverse case. - T3 (chain semantics): buildFetchHandler chains cfgBrowserManager.onDisconnect, preserving any caller-set handler. Uses .rejects.toThrow for the async shutdown path. - T4 (tunnelActive): tunnel-active blocks idle-shutdown even in headless mode. - T5 (static guard): exactly 3 module-level lifecycle sites use activeBrowserManager.getConnectionMode() — idleCheckTick, parent watchdog, SIGTERM. Catches refactor-introduced regressions before CI. Reuses existing makeMinimalConfig() + __resetRegistry() patterns from the factory contract tests. New makeMockBrowserManager() helper. beforeEach also resets module state via setTunnelActive, setLastActivity, and resetShutdownState from __testInternals__. Also deletes the old 'idle check skips in headed mode' string-grep test from browse/test/sidebar-ux.test.ts at line 1596. That test would have passed even with the dual-instance bug present (grepped for "=== 'headed'" + 'return' in the same window). Behavioral coverage moved to server-factory.test.ts. Verified: 33/33 tests pass in browse/test/server-factory.test.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
E2E Evals: ✅ PASS6/6 tests passed | $1.11 total cost | 12 parallel runners
12x ubicloud-standard-8 (Docker: pre-baked toolchain + deps) | wall clock ≈ slowest suite |
…wser # Conflicts: # CHANGELOG.md # VERSION
…wser # Conflicts: # CHANGELOG.md # VERSION
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four module-level lifecycle handlers in
browse/src/server.ts(idleCheckTick, parent watchdog, SIGTERM handler,onDisconnectdefault wire) read the module-levelBrowserManagerdirectly. Embedders (gbrowser's phoenix overlay) pass their own instance intobuildFetchHandler({ browserManager: ... }), so the module-level instance never haslaunchHeaded()called on it.connectionModestays'launched'forever, headed-mode early-returns never fire, and after 30 minutes of HTTP idle the server self-terminates out from under the still-open overlay window.Fix: introduce
let activeBrowserManager: BrowserManagerat module scope (symmetric with the existinglet activeShutdownpattern).buildFetchHandlerretargets it atcfg.browserManagerand CHAINScfg.browserManager.onDisconnecttoactiveShutdown— preserving any caller-installed handler instead of clobbering it.3-commit bisect:
fix(browse):6 edit sites inbrowse/src/server.ts(Edits 1, 2, 3, 4, 5-verify, 6) +__testInternals__export.test(browse):5 new behavioral tests inbrowse/test/server-factory.test.ts+ delete the broken string-grep test frombrowse/test/sidebar-ux.test.ts.chore:VERSION + CHANGELOG for v1.43.3.0.Test Coverage
Tests run: 33/33 pass in
browse/test/server-factory.test.ts; 50/50 across browser-manager-unit + server-factory.Pre-Landing Review
/plan-eng-reviewran clean (0 arch / 0 quality / 0 perf issues; 2 test gaps surfaced via D1 + D2 and resolved with T3 + T4)./codexplan review caught 6 substantive issues the static eng review missed: Bun memoizes dynamic imports,initRegistrytoken-reuse throws,shutdown()is async (sync.toThrow()cannot catch the rejection),cfg.browserManager.onDisconnectis a public field callers may set (would have been silently clobbered by a bare reassign), the original plan missed the SIGTERM site at line 1186, and tests belonged inserver-factory.test.tsnotsidebar-ux.test.ts. All 6 verified against actual code and incorporated into the shipped plan. The chain semantics in Edit 4, the SIGTERM site in Edit 6, the__testInternals__surface (includingresetShutdownState+setLastActivity), the test relocation, and the.rejects.toThrowassertion all trace to Codex findings.Plan Completion
All plan items DONE: 6 production edits applied (
browse/src/server.ts:601, 658, 705, 1212, 1387, 1394), 5 behavioral tests added (browse/test/server-factory.test.ts:388–530), broken string-grep test deleted (browse/test/sidebar-ux.test.ts:1596), VERSION + CHANGELOG updated. Sanity-grep confirms exactly 3 module-levelactiveBrowserManager.getConnectionMode()reads (idleCheckTick, watchdog, SIGTERM) and 0 stray module-levelbrowserManager.getConnectionMode()reads outsidehandleCommandInternalImplresponse payloads (which report mode informationally and are out of scope for lifecycle).Test plan
bun test browse/test/server-factory.test.ts— 33/33 pass (5 new + 28 existing factory contract)bun test browse/test/browser-manager-unit.test.ts— 17/17 passactiveBrowserManager.getConnectionMode()sites (idleCheckTick, watchdog, SIGTERM); 0 stray module-level reads outside handleCommand response payloadscfgBrowserManager.onDisconnectchain (caller cb first, then gstack shutdown)🤖 Generated with Claude Code