fix(browser): boot reconciliation must not orphan another replica's live session (F-06) - #273
Open
anilguleroglu wants to merge 1 commit into
Open
fix(browser): boot reconciliation must not orphan another replica's live session (F-06)#273anilguleroglu wants to merge 1 commit into
anilguleroglu wants to merge 1 commit into
Conversation
…ive session (F-06) reconcileOrphanedBrowserSessions decided a session was dead purely on whether THIS process's in-memory browserManager knew about it. A replica that just booted knows about none of them, so it marked every active-status session across every tenant "expired" the moment it ran -- including ones genuinely alive on a different, still-running replica. An isolated check reproduced this: a second node's empty local session map was enough to write another node's live session as expired. The cluster already has what this needed: a node registry with heartbeat + online/offline tracking (src/lib/core/cluster). Sessions now record which node's browserManager actually launched them (ownerNode, stamped once in the single service function all six call sites already funnel through -- browserSessionService.createBrowserSession -- so no call site had to remember to set it). Boot reconciliation reads the node registry once per sweep and only expires a session when its owner node is not currently online, or the session predates this field (pre-migration rows keep the old behavior, matching what was already correct for a genuinely single-node deployment). Scoped to browser sessions only. crawlerJobReconciler.ts has the exact same bug pattern (requeues every 'running'/'queued' job tenant-wide with no owner/heartbeat check, and can delete partial results) but is a separate, larger fix -- job requeue has failure modes session-expiry doesn't (in-flight partial results, retry semantics) and deserves its own review rather than being folded into this diff. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KQq6TnVNHRNU6Wz1eQzPpD
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
P1 finding from the 2026-09-05 finance-institution assessment.
reconcileOrphanedBrowserSessionsdecided a session was dead purely on whether THIS process's in-memorybrowserManagerknew about it. A replica that just booted knows about none of them, so it marked every active-status session across every tenant "expired" the moment it ran — including sessions genuinely alive on a different, still-running replica. The assessment's isolated check reproduced exactly this: a second node's empty local session map was enough to write another node's live session as expired.The cluster already has the infrastructure this needed — a node registry with heartbeat + online/offline tracking (
src/lib/core/cluster). This PR:ownerNodetoIBrowserSession(SQLite migration via the existingensureTableColumnmechanism + schema.ts for fresh installs; Mongo needs no schema change).browserSessionService.createBrowserSession) — so no call site had to remember to set it.reconcileOrphanedBrowserSessionsnow reads the node registry once per sweep (listClusterNodes({status:'online'})) and only expires a session when its owner node is not currently online, or the session predates this field (pre-migration rows keep the exact prior behavior — no regression for the single-node case this already worked correctly for).Explicitly out of scope for this PR
crawlerJobReconciler.tshas the exact same bug pattern (requeues everyrunning/queuedjob tenant-wide with no owner/heartbeat check, and can delete partial results) — but job requeue has failure modes session-expiry doesn't (in-flight partial results, retry/idempotency semantics), so it deserves its own review rather than being folded into this diff. Flagging as a direct follow-up using the identicalownerNode+ node-registry pattern proven here.Test plan
browser-operations-reconcile.test.ts: owner node still online → not touched (the exact F-06 scenario), owner node gone → expired, legacy row with noownerNode→ expired (matches prior behavior), session known to localbrowserManager→ never touched regardless of registry statenpx tsc --noEmitcleannpx eslintcleanvitest run(includingdb-parity.test.ts, which exercises the SQLite/Mongo schema in both backends): 5050 passed, 0 failed, 5 skipped🤖 Generated with Claude Code
https://claude.ai/code/session_01KQq6TnVNHRNU6Wz1eQzPpD