← Commit history

v2.0.257: Never kill a profile's Chrome to free its lock while pup has live sessions on that profile. This is what actually destroyed three of John's windows today. The pre-launch cleanup kills whatever holds the profile lock, authorized by an INFERENCE: a lock exists and I could not reconnect just now, therefore the holder is a dead orphan from an unclean shutdown. The reconnect gives up after a 3s probe plus a 5s puppeteer.connect, and on a loaded machine a healthy Chrome misses those deadlines, so pup declared the LIVE shared adom-you Chrome an orphan and killed it, taking two other threads' dashboards and the ab web-demo window John was actively using; pup then relaunched them and reconciled them away as closed, so from his side they vanished. Ground truth beats inference: if any live session is bound to the profile, that process is serving the user's windows, so pup now retries the reconnect patiently (20s) and adopts it, and if that fails it fails THIS launch honestly instead of destroying work that is not ours to destroy. A failed launch costs a retry; a wrong kill costs windows the user cannot get back.

John Lauer ·6218aebb85 ·2mo ago ·parent aa4f0a3
4 files changed +57−13
adom-bridge-puppeteer-manifest.json+5−5
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "2.0.256",-  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.256/adom-bridge-puppeteer-v2.0.256.zip",-  "sha256": "315928c415b45c0d6a0b32420c8aa0964173c0f80e1a1ca0616c8c70fac4975a",-  "size": 952824,+  "version": "2.0.257",+  "url": "https://wiki.adom.inc/download/adom/pup-bridge/2.0.257/adom-bridge-puppeteer-v2.0.257.zip",+  "sha256": "05234391cf6b97d117770632dbb085adc04594f63a33e7f041a40cdd90d38d38",+  "size": 953955,   "verbPrefixes": [     "browser_",     "desktop_recorder_",@@ -12,5 +12,5 @@   ],   "healthEndpoint": "/status",   "statusVerb": "browser_readiness",-  "released_at": "2026-08-20T11:05:49.142324Z"+  "released_at": "2026-08-20T11:08:49.401929Z" }
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-2.0.256\ No newline at end of file+2.0.257\ No newline at end of file
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "2.0.256",+  "version": "2.0.257",   "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. pup_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. pup_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job. macOS (v1.9.74+): full parity with mac-native mechanics \u2014 background-by-default is the app-level hide (windows stay drivable/screenshottable while hidden), raise/lower map to unhide+activate/hide, pup_alert_window posts a native toast whose click focuses the window, pup_use {browser:'chrome',install:true} installs Chrome from the official dmg, and taskbar identity/badging remains Windows-only.",   "homepage": "https://wiki.adom.inc/adom/adom-bridge",   "author": "Adom Inc.",
src/server.js+50−6
@@ -9737,10 +9737,47 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer       const lockFile = path.join(userDataDir, 'SingletonLock');       const lockExists = fs.existsSync(lockFile) || fs.existsSync(path.join(userDataDir, 'lockfile'));       if (lockExists) {-        console.log(`Profile "${profileName}" has a lock file — checking for orphaned Chrome...`);-        await killBrowserProcess(null, profileName);-        // Give OS time to release the lock-        await new Promise(r => setTimeout(r, 1000));+        // v2.0.257 — THE BUG THAT COST JOHN THREE WINDOWS (2026-08-20). This kill is authorized by an+        // INFERENCE: "a lock file exists and I could not reconnect just now, therefore the holder is a+        // dead orphan from an unclean shutdown." That inference is wrong in the one case where it is+        // catastrophic. The reconnect above gives up after a 3s probe + a 5s puppeteer.connect, and on+        // a loaded machine a perfectly healthy Chrome misses those deadlines — so pup declared the LIVE+        // shared adom-you Chrome an orphan and killed it, taking three windows with it (two other+        // threads' dashboards and the ab web-demo window John was actively using). pup then relaunched+        // them and reconciled them away as "closed", so from John's side they simply vanished.+        // GROUND TRUTH beats inference: if any live session is bound to this profile, that process is+        // not an orphan — it is the process serving the user's windows. Never kill it. Try one patient+        // reconnect instead, and if that fails, fail THIS launch honestly rather than destroying work+        // that is not ours to destroy. A failed launch costs a retry; a wrong kill costs the user's+        // windows, and they have no way to get them back.+        let _liveOnProfile = 0;+        for (const _s of sessions.values()) { if (_s.profileName === profileName) _liveOnProfile++; }+        if (_liveOnProfile > 0) {+          console.log(`Profile "${profileName}" lock is held while ${_liveOnProfile} live pup session(s) are bound to it — that Chrome is NOT an orphan, it is serving the user's windows. Refusing to kill it; retrying the reconnect patiently.`);+          let _reconnected = null;+          try {+            const _pf = path.join(userDataDir, 'DevToolsActivePort');+            const _p = parseInt(String(fs.readFileSync(_pf, 'utf8')).trim().split('\n')[0], 10);+            if (_p && !isNaN(_p)) {+              _reconnected = await Promise.race([+                puppeteer.connect({ browserURL: `http://127.0.0.1:${_p}`, defaultViewport: null }),+                new Promise((_, rej) => setTimeout(() => rej(new Error('patient reconnect timeout')), 20000)),+              ]);+              _reconnected._cdpPort = _p;+            }+          } catch (e) { console.log(`Patient reconnect to profile "${profileName}" failed too (${e.message})`); }+          if (_reconnected) {+            console.log(`Patient reconnect to profile "${profileName}" SUCCEEDED — the browser was healthy, just slow. Nothing was killed.`);+            browser = _reconnected;+          } else {+            throw new Error(`profile "${profileName}" is in use by ${_liveOnProfile} live pup session(s) and its Chrome could not be reconnected. Refusing to kill the browser hosting those windows just to free the profile lock — that would destroy windows other threads (or the user) are using. Retry in a moment, or close those sessions first with pup_close_window.`);+          }+        } else {+          console.log(`Profile "${profileName}" has a lock file and NO live sessions are bound to it — checking for orphaned Chrome...`);+          await killBrowserProcess(null, profileName);+          // Give OS time to release the lock+          await new Promise(r => setTimeout(r, 1000));+        }       }        // Pup is AI-driven, never human-driven, so Chrome's "restore last@@ -9749,12 +9786,15 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer       // running heavy content (3D viewers, animations) under the       // anti-throttle flags we set, eating CPU. Wipe the session-restore       // state before launch — the AI knows what it wants open.-      prepareCleanProfile(userDataDir);+      // v2.0.257: skip when the patient reconnect above adopted the live browser — that profile is+      // IN USE and wiping its session-restore state (or relaunching over it) is exactly the damage+      // this fix exists to prevent.+      if (!browser) prepareCleanProfile(userDataDir);       // Give the profile a visible Adom identity in Chrome's profile chip. The       // shared durable jar is branded "Adom"; a named isolated jar carries its       // own name so the user can still tell windows apart. Both wear the       // Adom-logo avatar so ANY pup window is visually distinct from real Chrome.-      brandPupProfile(userDataDir, profileName,+      if (!browser) brandPupProfile(userDataDir, profileName,         profileName === DEFAULT_PROFILE ? 'Adom'           : (/^iso-.*-pub$/.test(profileName) ? 'Adom · anonymous' : ('Adom · ' + profileName)));     }@@ -9986,6 +10026,10 @@ async function getOrLaunchBrowser(profileName, { freshProfile = false, strictPer     const launchList = candidates.length ? candidates : [{ kind: 'default', source: 'puppeteer', executablePath: null }];      for (const cand of launchList) {+      // v2.0.257: the patient reconnect above may have adopted the profile's LIVE browser rather than+      // letting pup kill it. In that case there is nothing to launch — relaunching over a profile that+      // is in use is the very damage being prevented.+      if (browser) break;       if (cand.executablePath) launchOpts.executablePath = cand.executablePath;       else delete launchOpts.executablePath;       // v2.0.53: the toolbar rides ONLY on CfT (branded Chrome ignores/removed the flag).