pup-bridge-macos
Public Made by Adomby adom
The pup for macOS SKILL PACK: the AI skills that teach a cloud/container AI how to drive the macOS Puppeteer (pup) bridge (pup_* verbs): the everyday `pup` skill plus focused sub-skills for windows/sessions/tabs, screenshots+recording, credentials, lanes, settings, browsers+Chrome install, vendor logins and the Adom wiki. Installs into ~/.claude/skills + ~/.codex/skills. DOCS ONLY: this is NOT the bridge runtime. The bridge itself is one signed macOS binary that Adom Bridge for macOS installs an
name: pup-lanes user-invocable: false description: "Parent skill: pup. The two ways pup drives a pup window (never name the browser build to a user), the extension lane (DEFAULT, every vendor site loads, no debug port) and the CDP lane (opt-in ONLY via lane:'cdp', full DevTools control but detectable and walled by vendor edges). Pros and cons of each, exactly which verbs need CDP, what lane:'cdp' costs (it takes adom-you away from the extension lane and closes its windows), and the rule: the extension lane already has CDP-level access (pup_devtools sends any DevTools command), so never stop or move a task because the CDP lane is off. Read before passing lane, isolated, profile, highFps or webSecurity to pup_open_window, and whenever a verb answers lane_needs_cdp or cdp_lane_required. Trigger words: pup lane, extension lane, cdp lane, lane:cdp, lane_needs_cdp, cdp_lane_required, which lane, remote-debugging-port, debug port detected, site blocks pup, pup_open_window lane, isolated:true refused, highFps refused, three icons vs four icons."
pup lanes: the extension lane is pup. CDP is the exception.
The point of the September 2026 work (John, 2026-09-13 and 14) was one thing: pup windows that load
EVERY website without the site blocking us. That turned out to be the extension lane. So it is the
default, and the CDP lane opens only when an AI writes lane:"cdp" because the task needs something
only CDP can do AND the user has turned that lane on for the box (cdpLane, off by default since 2.0.652:
cdp_lane_disabled otherwise). pup no longer falls back to CDP on its own for any reason.
What each lane is
| Extension lane (DEFAULT) | CDP lane (lane:"cdp", opt-in) |
|
|---|---|---|
| How pup drives the window | pup's own MV3 extension polls the bridge and runs the commands inside Chrome | Chrome DevTools Protocol over --remote-debugging-port |
| Debug port | none | yes, and vendor edges detect it (measured: st.com and mouser RST the HTTP/2 stream, arrow denies) |
| Profile | one browser per profile: adom-you is shared by every thread; isolated:true and profile:"<name>" get their own lane browser, extension and queue (2.0.659) |
any profile; on adom-you it TAKES the profile from the extension lane and closes those windows |
| Toolbar | four icons: pup dots, pen, identity tile, nbe | three icons (nbe rides the extension lane only) |
| Taskbar identity, tiles, overlays, jump lists | none on macOS (no per-window taskbar button) | none on macOS |
| Annotate (pen, snip) | yes (2.0.569, Trusted-Types safe since 2.0.570) | yes |
| Vendor sites (st, mouser, digikey, arrow, analog, ti, avnet, nxp, infineon, and the rest of the 20) | load | walled by several of them |
Pros and cons
Extension lane
- Pro: sites load. This is the reason it exists and the reason it is the default.
- Pro: one shared adom-you browser, so logins and cookies accumulate for every thread, and every window carries the full toolbar including nbe.
- Con:
pup_evalruns in the page's MAIN world, so a page with a strict Content-Security-Policy (wiki.adom.inc, claude.ai) refuses it with "Evaluating a string as JavaScript violates the following Content Security Policy". Usepup_screenshot,pup_list_tabs,pup_navigateand the other DOM-free verbs there, orlane:"cdp"if you truly must run script on such a page and accept that vendor sites will not load in that window. - Note on screenshots: a window that produces no frames cannot be captured. On macOS the windows of a hidden app stop producing frames after about a minute, so every capture briefly unhides pup's own browser app BEHIND the user's frontmost app (no activation, never over the user's window) and re-hides it afterwards, one shared unhide for concurrent captures.
captureVisibleTabis raced against a 3 s deadline and falls back to a debugger frame. The lane also launches withCalculateNativeWinOcclusiondisabled, so a covered window keeps compositing. Background TABS are still throttled by Chrome as before; this is not the highFps anti-throttle set. - Since 2.0.578 the extension lane ALSO has trusted input (
pup_click,pup_hover,pup_type,pup_press_key,pup_scroll,pup_fill), element and full-resolution screenshots,pup_select(a<select>by value or label, 2.0.592), and CSP-proofpup_eval(a page whose Content-Security-Policy or Trusted Types policy refuses the injected eval is retried through DevTools automatically, 2.0.591; Google Calendar and Admin are the everyday cases). They ride nbe's DevTools driver inside the lane (chrome.debugger, attached to one tab on demand, detached after nbe's idle window; no debug port, no automation flag,navigator.webdriverstays false, no bar). Since 2.0.581 recording (pup_record_start/pup_record_stop) works on this lane too: it rides ab's native recorder (Windows Graphics Capture, hardware H.264 MP4, keyed on the window handle), which records a covered window live and emits frames only when the content changes. Still CDP-only: selector-drivenpup_download,realKeystrokeslogins, iframe targeting, separate profiles (backlog: pup-bridge#113),highFps,webSecurity:false. Those answerlane_needs_cdpwith the fix.
CDP lane
- Pro: everything DevTools can do: trusted mouse and keyboard events, cross-origin iframes,
Runtime.evaluatethat ignores page CSP, element and full-res screenshots, annotate, recording, emulation, anti-throttle for a live view. - Con: the debug port is a detection surface. A CDP window is what gets you "Access Denied" on the hard vendor sites.
- Con:
lane:"cdp"on adom-you evicts the extension lane: pup closes every extension-lane window to hand the profile over, because Chrome runs one process per profile. Other threads lose their windows. That is why it is opt-in. - Con: three toolbar icons, no nbe.
The rule
- Open windows with no
lane. You get the extension lane. profileandisolatedneed no lane (2.0.659: their own extension-lane browser). OnlyhighFpsandwebSecurity:falsestill needlane:"cdp"; without it pup refuses withcdp_lane_requiredand says why. Since 2.0.563 nothing silently opens on CDP.- You almost never need
lane:"cdp": the extension lane speaks the full DevTools protocol through pup's extension, andpup_devtools {method, params}sends any command to an extension-lane window. Since 2.0.726,lane:"cdp"on a box with the CDP lane off opens on the extension lane with a_laneNoteinstead of refusing; never stop, and never move the task to another box, because CDP is off. A pup update reaches a running extension lane on its own: pup re-stages the extension, cycles the lane browser and relaunches every window from its file on the new extension (2.0.647; the 2.0.569 in-place reload broke the channel and is gone), so a new build takes effect without anyone closing a window.pup_status.extensionLane.workerVersionshows what the lane is running. - If a verb says something is not wired on the extension lane yet, its hint names the extension-lane way (a URL download,
pup_typefor a stubborn form,pup_devtoolsfor iframes and anything else). Use that, and file the gap on adom/pup-bridge.
How you tell them apart
The toolbar: four icons is the extension lane, three is CDP (John's own spec for the fourth icon). pup_list_windows reports transport: "ext" or "cdp" per window. pup_status.extensionLane shows the lane's health.
What the extension lane can do since 2.0.648 (it used to say lane_needs_cdp)
- Trusted input:
pup_input_dispatch {type:"click"|"mouseMoved"|"mousePressed"|"mouseReleased"|"mouseWheel"|"keyDown"|"keyUp"|"rawKeyDown"|"char", x, y, ...}works on the extension lane through the worker's DevTools driver, so the event is as trusted as the CDP lane's and lands inside cross-origin frames. Measured 2026-09-19: a click on the reCAPTCHA demo checkbox passed it and issued a token, no image grid. Captchas are driven here, never on the CDP lane and never handed to the human (the user's rule); the image-grid loop (screenshot, pick tiles, trusted tile clicks, Verify) is the caller's script on top of these verbs. pup_fetch_urlruns in the named tab on this lane, tries the fetch with credentials and then without (a wildcard CORS header refuses credentials), returnshttpStatus,contentType,bytes,sha256, and the body asbodyBase64(binary-safe) ortext.saveTotakes your container path: the CLI writes the file from the returned body and reportssavedToplussavedToFilesystem:"container"; comparesha256(measured: a 269669-byte PDF, identical hash to the reporter's). A cancelled HTTP auth challenge comes back asno_credentialwith the host.isolated:truemints a throwawayiso-<id>profile (reported bypup_window_info), opens it in its OWN extension-lane browser (2.0.659, pup-bridge#113), and ends that browser and removes the profile when the window closes. Nolaneargument, no CDP. Measured: an isolated window on the extension lane read an empty cookie jar while a default-lane window kept working beside it.- Slow boxes: a cold
pup_open_windowthat would exceed ab's budget answersok:true, loading:truewith a progress frame once the window exists and finishes the navigate in the background; pollpup_window_infoinstead of opening again.
The CDP lane is OFF by default (2.0.652, John 2026-09-19)
pup_open_window {lane:"cdp"} opens on the extension lane (with a _laneNote) unless the user has turned the CDP lane
on for that box with pup_configure {cdpLane:"on"}; before 2.0.726 it refused with cdp_lane_disabled, and AIs read
that as "pup can't do this here". John: "i want it almost NEVER used cuz then you may use it as a crutch instead of
fixing ext lane to solve all use cases." Trusted input, HTTP auth, fetch, screenshots and recording all run on the
extension lane; a feature that genuinely needs CDP is an extension-lane gap to file on adom/pup-bridge, not a reason
to open a CDP window. isolated:true and profile:"<name>" run on the extension lane too (2.0.659): each profile gets its own lane browser, its own staged extension and queue, so an isolated window is a separate browser with an empty jar, and a throwaway one ends with its last window.
Windows keep their profile and their record across restarts (2.0.669 to 2.0.671)
Each profile has its own extension-lane browser (adom-you, plus one per explicit profile). On
2026-09-21 John saw windows lose their record after an upgrade on his laptop, and the cause was two bugs:
- A window still on screen was forgotten. After an upgrade the recovery pass counted each 15 s "still on screen, extension not answering yet" check as a failed relaunch and deleted the window's record on the third. The window stayed up with no owner, and its thread opened a duplicate. Since 2.0.669 a window on screen is never counted as a failure; its record is kept until it is re-adopted.
- A browser was adopted into the wrong profile. At boot pup restored only the adom-you lane, and the first unknown lane browser to say hello became adom-you, whatever folder it ran. adom-you windows then opened inside the "services" browser, with that profile's cookies. Since 2.0.670/671: every profile's lane is restored at boot and trusted only when its processes really run that profile's folder; the extension reports its profile in every hello, and an unknown browser is adopted only into that profile's lane, only after the boot restore; each window is re-adopted on its own profile's lane; and a window found inside another profile's browser is closed there and reopened in its own profile, at its page, in the background.
Proven on CRR with an adom-you window plus a second-profile window through an upgrade and a plain restart (both kept, each in its own profile's browser, same windows), and on AdomLapper, where the three misplaced adom-you windows were moved back into the adom-you browser automatically.
Multi-tab windows after an upgrade (2.0.676). An upgrade cycles the lane browser and reopens every window with its tabs. Two bugs showed on AdomLapper on 2026-09-22: the extension opened a new tab in whatever window had a real page when it could not find the named one, so 6 of one thread's tabs landed in another thread's window; and the reopen de-duplicated URLs with the query removed, so different pages on one path (two YouTube videos) collapsed into one. Now a new tab opens only in the window pup names (a live tab of it, or its Chrome window id) and the extension refuses with window_not_found rather than guess; duplicates are exact URLs only. Proven on CRR: a 4-tab window and a 1-tab window from another thread both came back complete and separate.
The user's close is final, even during a restart or an upgrade (2.0.684)
On 2026-09-23 John closed the NSF thread's window on his laptop and it reopened, twice. An upgrade had ended pup's browser and was relaunching his windows one at a time; each close landed between two relaunches, so the thread's next window came back 13 to 28 s later. The rules now:
- pup relaunches a missing window ONLY when it knows that window's browser died for this pass: pup ended it (an upgrade or pup_restart_browser), it crashed, or it was already dead when pup booted. Those windows are marked lost before anything relaunches.
- Any other missing window, one whose browser is running without it, was CLOSED. pup forgets it and never brings it back. This covers a close that happens while pup itself is restarting and cannot hear the extension.
- Closing a window while a relaunch queue runs cancels every queued relaunch of the same thread, and each relaunch first picks up closes reported since the previous one.
Do not decide "closed vs lost" from whether a browser is running NOW: the first relaunch of an upgrade restarts the browser, and 2.0.681, which did exactly that, dropped every window after the first on every upgrade (caught on CRR before it went public). Proven on CRR with three windows of one thread: an upgrade brings all three back, closing the first mid-queue cancels the other two, and a window closed during a restart stays closed.
---
name: pup-lanes
user-invocable: false
description: "Parent skill: pup. The two ways pup drives a pup window (never name the browser build to a user), the extension lane (DEFAULT, every vendor site loads, no debug port) and the CDP lane (opt-in ONLY via lane:'cdp', full DevTools control but detectable and walled by vendor edges). Pros and cons of each, exactly which verbs need CDP, what lane:'cdp' costs (it takes adom-you away from the extension lane and closes its windows), and the rule: the extension lane already has CDP-level access (pup_devtools sends any DevTools command), so never stop or move a task because the CDP lane is off. Read before passing lane, isolated, profile, highFps or webSecurity to pup_open_window, and whenever a verb answers lane_needs_cdp or cdp_lane_required. Trigger words: pup lane, extension lane, cdp lane, lane:cdp, lane_needs_cdp, cdp_lane_required, which lane, remote-debugging-port, debug port detected, site blocks pup, pup_open_window lane, isolated:true refused, highFps refused, three icons vs four icons."
---
# pup lanes: the extension lane is pup. CDP is the exception.
The point of the September 2026 work (John, 2026-09-13 and 14) was one thing: pup windows that load
EVERY website without the site blocking us. That turned out to be the extension lane. So it is the
default, and the CDP lane opens only when an AI writes `lane:"cdp"` because the task needs something
only CDP can do AND the user has turned that lane on for the box (`cdpLane`, off by default since 2.0.652:
`cdp_lane_disabled` otherwise). pup no longer falls back to CDP on its own for any reason.
## What each lane is
| | Extension lane (DEFAULT) | CDP lane (`lane:"cdp"`, opt-in) |
|---|---|---|
| How pup drives the window | pup's own MV3 extension polls the bridge and runs the commands inside Chrome | Chrome DevTools Protocol over `--remote-debugging-port` |
| Debug port | none | yes, and vendor edges detect it (measured: st.com and mouser RST the HTTP/2 stream, arrow denies) |
| Profile | one browser per profile: adom-you is shared by every thread; `isolated:true` and `profile:"<name>"` get their own lane browser, extension and queue (2.0.659) | any profile; on adom-you it TAKES the profile from the extension lane and closes those windows |
| Toolbar | four icons: pup dots, pen, identity tile, nbe | three icons (nbe rides the extension lane only) |
| Taskbar identity, tiles, overlays, jump lists | none on macOS (no per-window taskbar button) | none on macOS |
| Annotate (pen, snip) | yes (2.0.569, Trusted-Types safe since 2.0.570) | yes |
| Vendor sites (st, mouser, digikey, arrow, analog, ti, avnet, nxp, infineon, and the rest of the 20) | load | walled by several of them |
## Pros and cons
**Extension lane**
- Pro: sites load. This is the reason it exists and the reason it is the default.
- Pro: one shared adom-you browser, so logins and cookies accumulate for every thread, and every window carries the full toolbar including nbe.
- Con: `pup_eval` runs in the page's MAIN world, so a page with a strict Content-Security-Policy (wiki.adom.inc, claude.ai) refuses it with "Evaluating a string as JavaScript violates the following Content Security Policy". Use `pup_screenshot`, `pup_list_tabs`, `pup_navigate` and the other DOM-free verbs there, or `lane:"cdp"` if you truly must run script on such a page and accept that vendor sites will not load in that window.
- Note on screenshots: a window that produces no frames cannot be captured. On macOS the windows of a hidden app stop producing frames after about a minute, so every capture briefly unhides pup's own browser app BEHIND the user's frontmost app (no activation, never over the user's window) and re-hides it afterwards, one shared unhide for concurrent captures. `captureVisibleTab` is raced against a 3 s deadline and falls back to a debugger frame. The lane also launches with `CalculateNativeWinOcclusion` disabled, so a covered window keeps compositing. Background TABS are still throttled by Chrome as before; this is not the highFps anti-throttle set.
- Since 2.0.578 the extension lane ALSO has trusted input (`pup_click`, `pup_hover`, `pup_type`, `pup_press_key`, `pup_scroll`, `pup_fill`), element and full-resolution screenshots, `pup_select` (a `<select>` by value or label, 2.0.592), and CSP-proof `pup_eval` (a page whose Content-Security-Policy or Trusted Types policy refuses the injected eval is retried through DevTools automatically, 2.0.591; Google Calendar and Admin are the everyday cases). They ride nbe's DevTools driver inside the lane (`chrome.debugger`, attached to one tab on demand, detached after nbe's idle window; no debug port, no automation flag, `navigator.webdriver` stays false, no bar). Since 2.0.581 recording (`pup_record_start` / `pup_record_stop`) works on this lane too: it rides ab's native recorder (Windows Graphics Capture, hardware H.264 MP4, keyed on the window handle), which records a covered window live and emits frames only when the content changes. Still CDP-only: selector-driven `pup_download`, `realKeystrokes` logins, iframe targeting, separate profiles (backlog: pup-bridge#113), `highFps`, `webSecurity:false`. Those answer `lane_needs_cdp` with the fix.
**CDP lane**
- Pro: everything DevTools can do: trusted mouse and keyboard events, cross-origin iframes, `Runtime.evaluate` that ignores page CSP, element and full-res screenshots, annotate, recording, emulation, anti-throttle for a live view.
- Con: the debug port is a detection surface. A CDP window is what gets you "Access Denied" on the hard vendor sites.
- Con: `lane:"cdp"` on adom-you evicts the extension lane: pup closes every extension-lane window to hand the profile over, because Chrome runs one process per profile. Other threads lose their windows. That is why it is opt-in.
- Con: three toolbar icons, no nbe.
## The rule
1. Open windows with no `lane`. You get the extension lane.
2. `profile` and `isolated` need no lane (2.0.659: their own extension-lane browser). Only `highFps` and `webSecurity:false` still need `lane:"cdp"`; without it pup refuses with `cdp_lane_required` and says why. Since 2.0.563 nothing silently opens on CDP.
3. You almost never need `lane:"cdp"`: the extension lane speaks the full DevTools protocol through pup's extension, and `pup_devtools {method, params}` sends any command to an extension-lane window. Since 2.0.726, `lane:"cdp"` on a box with the CDP lane off opens on the extension lane with a `_laneNote` instead of refusing; never stop, and never move the task to another box, because CDP is off.
A pup update reaches a running extension lane on its own: pup re-stages the extension, cycles the lane browser and relaunches every window from its file on the new extension (2.0.647; the 2.0.569 in-place reload broke the channel and is gone), so a new build takes effect without anyone closing a window. `pup_status.extensionLane.workerVersion` shows what the lane is running.
4. If a verb says something is not wired on the extension lane yet, its hint names the extension-lane way (a URL download, `pup_type` for a stubborn form, `pup_devtools` for iframes and anything else). Use that, and file the gap on adom/pup-bridge.
## How you tell them apart
The toolbar: four icons is the extension lane, three is CDP (John's own spec for the fourth icon). `pup_list_windows` reports `transport: "ext"` or `"cdp"` per window. `pup_status.extensionLane` shows the lane's health.
## What the extension lane can do since 2.0.648 (it used to say lane_needs_cdp)
- **Trusted input:** `pup_input_dispatch {type:"click"|"mouseMoved"|"mousePressed"|"mouseReleased"|"mouseWheel"|"keyDown"|"keyUp"|"rawKeyDown"|"char", x, y, ...}` works on the extension lane through the worker's DevTools driver, so the event is as trusted as the CDP lane's and lands inside cross-origin frames. Measured 2026-09-19: a click on the reCAPTCHA demo checkbox passed it and issued a token, no image grid. Captchas are driven here, never on the CDP lane and never handed to the human (the user's rule); the image-grid loop (screenshot, pick tiles, trusted tile clicks, Verify) is the caller's script on top of these verbs.
- **`pup_fetch_url`** runs in the named tab on this lane, tries the fetch with credentials and then without (a wildcard CORS header refuses credentials), returns `httpStatus`, `contentType`, `bytes`, `sha256`, and the body as `bodyBase64` (binary-safe) or `text`. `saveTo` takes your container path: the CLI writes the file from the returned body and reports `savedTo` plus `savedToFilesystem:"container"`; compare `sha256` (measured: a 269669-byte PDF, identical hash to the reporter's). A cancelled HTTP auth challenge comes back as `no_credential` with the host.
- **`isolated:true`** mints a throwaway `iso-<id>` profile (reported by `pup_window_info`), opens it in its OWN extension-lane browser (2.0.659, pup-bridge#113), and ends that browser and removes the profile when the window closes. No `lane` argument, no CDP. Measured: an isolated window on the extension lane read an empty cookie jar while a default-lane window kept working beside it.
- **Slow boxes:** a cold `pup_open_window` that would exceed ab's budget answers `ok:true, loading:true` with a progress frame once the window exists and finishes the navigate in the background; poll `pup_window_info` instead of opening again.
## The CDP lane is OFF by default (2.0.652, John 2026-09-19)
`pup_open_window {lane:"cdp"}` opens on the extension lane (with a `_laneNote`) unless the user has turned the CDP lane
on for that box with `pup_configure {cdpLane:"on"}`; before 2.0.726 it refused with `cdp_lane_disabled`, and AIs read
that as "pup can't do this here". John: "i want it almost NEVER used cuz then you may use it as a crutch instead of
fixing ext lane to solve all use cases." Trusted input, HTTP auth, fetch, screenshots and recording all run on the
extension lane; a feature that genuinely needs CDP is an extension-lane gap to file on adom/pup-bridge, not a reason
to open a CDP window. `isolated:true` and `profile:"<name>"` run on the extension lane too (2.0.659): each profile gets its own lane browser, its own staged extension and queue, so an isolated window is a separate browser with an empty jar, and a throwaway one ends with its last window.
## Windows keep their profile and their record across restarts (2.0.669 to 2.0.671)
Each profile has its own extension-lane browser (adom-you, plus one per explicit `profile`). On
2026-09-21 John saw windows lose their record after an upgrade on his laptop, and the cause was two bugs:
- **A window still on screen was forgotten.** After an upgrade the recovery pass counted each 15 s
"still on screen, extension not answering yet" check as a failed relaunch and deleted the window's
record on the third. The window stayed up with no owner, and its thread opened a duplicate.
Since 2.0.669 a window on screen is never counted as a failure; its record is kept until it is re-adopted.
- **A browser was adopted into the wrong profile.** At boot pup restored only the adom-you lane, and the
first unknown lane browser to say hello became adom-you, whatever folder it ran. adom-you windows then
opened inside the "services" browser, with that profile's cookies. Since 2.0.670/671: every profile's lane
is restored at boot and trusted only when its processes really run that profile's folder; the extension
reports its profile in every hello, and an unknown browser is adopted only into that profile's lane, only
after the boot restore; each window is re-adopted on its own profile's lane; and a window found inside
another profile's browser is closed there and reopened in its own profile, at its page, in the background.
Proven on CRR with an adom-you window plus a second-profile window through an upgrade and a plain restart
(both kept, each in its own profile's browser, same windows), and on AdomLapper, where the three misplaced
adom-you windows were moved back into the adom-you browser automatically.
**Multi-tab windows after an upgrade (2.0.676).** An upgrade cycles the lane browser and reopens every window
with its tabs. Two bugs showed on AdomLapper on 2026-09-22: the extension opened a new tab in whatever window
had a real page when it could not find the named one, so 6 of one thread's tabs landed in another thread's
window; and the reopen de-duplicated URLs with the query removed, so different pages on one path (two YouTube
videos) collapsed into one. Now a new tab opens only in the window pup names (a live tab of it, or its Chrome
window id) and the extension refuses with window_not_found rather than guess; duplicates are exact URLs only.
Proven on CRR: a 4-tab window and a 1-tab window from another thread both came back complete and separate.
## The user's close is final, even during a restart or an upgrade (2.0.684)
On 2026-09-23 John closed the NSF thread's window on his laptop and it reopened, twice. An upgrade had ended pup's browser and was relaunching his windows one at a time; each close
landed between two relaunches, so the thread's next window came back 13 to 28 s later. The rules now:
- pup relaunches a missing window ONLY when it knows that window's browser died for this pass: pup ended it (an
upgrade or pup_restart_browser), it crashed, or it was already dead when pup booted. Those windows are marked
lost before anything relaunches.
- Any other missing window, one whose browser is running without it, was CLOSED. pup forgets it and never brings
it back. This covers a close that happens while pup itself is restarting and cannot hear the extension.
- Closing a window while a relaunch queue runs cancels every queued relaunch of the same thread,
and each relaunch first picks up closes reported since the previous one.
Do not decide "closed vs lost" from whether a browser is running NOW: the first relaunch of an upgrade restarts the
browser, and 2.0.681, which did exactly that, dropped every window after the first on every upgrade (caught on CRR
before it went public). Proven on CRR with three windows of one thread: an upgrade brings all three back, closing
the first mid-queue cancels the other two, and a window closed during a restart stays closed.