Skip to content

E2E quit check: time a quit from when it reaches main; check apps that quit on their own - #13

Merged
siyabendoezdemir merged 4 commits into
mainfrom
lead/harness
Oct 10, 2026
Merged

siyabendoezdemir merged 4 commits into
mainfrom
lead/harness

Conversation

@siyabendoezdemir

@siyabendoezdemir siyabendoezdemir commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Was stacked on PR #11 (base lead/quit). #11 has merged, so the base is main now. Four follow-ups from PR #11's review (reviews/lead-quit.md: F1, F2, F3 and F4), all about the e2e quit check (D28). Also the one bug F3 found in main's code (archive review F8).

What

  • F1: the 15 s clock starts when the quit reaches Orbit (tests/e2e/helpers.ts, src/main/index.ts).

    • Before: the clock started when the test called app.close(). Playwright does work of its own before it asks Orbit to quit; after a failed test the reviewer measured 5.0 s of it. On a runner about 3× slower, a healthy quit after a failed test could be killed, and the report would blame the quit.
    • Now: in e2e mode, main logs [orbit] quit requested in before-quit. Orbit gets 15 s from that line. If the line never comes, the kill happens 35 s after app.close().
    • A kill now says which happened: still running: it had the quit for 15 s (main thread: …) or still running: the quit never reached its main process (…).
  • F2: an app that quits on its own is checked too.

    • Before: when Orbit quit because its last window closed (TB-22's "closing the last window" test), nothing called app.close() on that app, so an exception it threw while quitting went unnoticed.
    • Now: such an app is remembered when its process closes, and the next launchOrbit() checks it before starting another. A test that ends without relaunching still checks it through its own app.close() in afterEach.
  • F4: on macOS, a kill shows the main thread's stack.

    • There's no /proc on macOS, so the kill reported main thread: unknown. The hangs that are still unexplained (CB-21, MW-14) happened there.
    • Now, before the kill, the harness runs sample <pid> 1 and puts the main thread's deepest 40 frames in the failure, with the path of the full sample.
    • Not run on macOS (no Mac here, and CI is off, D42). Any error falls back to unknown, as before.
  • F3: a promise rejection nothing handled fails the test too (src/main/index.ts).

    • Before: Electron only prints a warning for an unhandled rejection in the main process, so the check never saw one.
    • Now: main logs it with the same marker as an uncaught exception ([orbit] uncaught exception: (a promise rejection nothing handled) …), in every mode, like the exception handler. The check fails the test that caused it.
    • A new TB-22 test fails without the logging (Received string: "") and passes with it.
  • The rejection it found: a tab closed while its history restores (src/main/tabs/runtime.ts, archive review F8).

    • Waking a tab restores its history. If the tab closed before the restore finished, restore's catch loaded the page into contents already gone. loadURL threw Object has been destroyed, and nothing handled the rejection. The archive reviewer saw it after archive's restore tests.
    • load() now does nothing for destroyed contents.
    • A new TB-22 test closes a tab while its restore waits on a page that never answers. It fails without the fix (TypeError: Object has been destroyed) and passes with it, 3 of 3.
    • A full run with the new check found no other rejection in main's code (below). Lanes will meet it for their own code when they merge this.

Checks

  • Scratch spec, run on both harnesses (deleted afterwards):
    • F1: a before-quit listener added in front of Orbit's busy-waits 17 s, so the quit reaches Orbit's own handler late.
      • Old harness (lead/quit): killed at 15.1 s: still running (main thread: 0). Fails.
      • New: app.close() took 17.2 s; not killed. Passes.
    • F2: a will-quit listener throws, then every window closes, so Orbit quits on its own; the test then relaunches.
      • Old harness: passes, and the exception goes unnoticed.
      • New: fails with Orbit's main process threw an exception nothing caught … boom while quitting alone.
  • Regression runs on this branch's code (1440×900, 1 worker), covering every spec file with a test that relaunches Orbit:
    • core, spaces, tabs-more, smoke and split: 116/116 passed (4.6 min);
    • commandbar: 36/36 (1.2 min);
    • tabs: 15/15 (49 s).
  • Full suite on 8223aa5 (1440×900, 1 worker): 221/221 passed (9.2 min), exit 0. No exception was logged and no quit needed the kill.
    • That includes TB-22's "closing the last window" test, where Orbit quits on its own and the relaunch now checks it. No exception was logged and no quit needed the kill.
  • Full suite with F3 and F8 on a151ced (1440×900, 1 worker): 222 passed, 1 failed (13.4 min). No test logged an uncaught exception or an unhandled rejection, and no quit needed the kill.
    • The failure is CB-22's 500-tab budget (commandbar.spec.ts:838: open took 73.5 ms against 50 ms). The load average was 41–49 on 16 cores; it passed alone right after.
  • npm run build, npm run typecheck, npm run lint: exit 0. Unit tests: 255/255.

Not in this PR

  • F5 (write the log to a file in packaged builds): a product change, for later.
  • F6 (guard Peek's settle timer like the strip's): it can't throw today, so no test can fail without it.

Merge with main (PRs #10, #11 and #9), and the local check (merge gate, D42)

GitHub Actions is off for cost, so a local check is the merge gate. PRs #10, #11 and #9 merged first. This branch is fast-forwarded to 87f488d, which merges this PR (a151ced) into da6a207, whose tree is main's now. main adds nothing on top of it, so 87f488d's tree is exactly what lands.

  • One conflict, in tests/e2e/helpers.ts: launchOrbit() keeps main's reducedMotion and args options, and checks an app that quit on its own (F2) before launching another.
  • Check on 87f488d (Linux, 1440×900, 1 worker):
    npm run build; npm run typecheck; npm run lint; ./node_modules/.bin/vitest run
    xvfb-run -a -s "-screen 0 1440x900x24" ./node_modules/.bin/playwright test --workers 1 --retries 0 --reporter=line
    
    • Build, typecheck and lint exit 0; unit 293/293.
    • E2E: 247 passed, 2 skipped, 0 failed (13.5 min). The two skipped are media's test.fixme tests for players inside frames (MD-01, MD-09).
    • No test logged an uncaught exception or an unhandled rejection, so media's code passes the new rejection check. No quit needed the kill.
  • macOS: unverified. F4's sample path has never run, and the macOS-only hangs (CB-21, MW-14) are still unexplained.

Review

reviews/lead-harness.md (an independent reviewer, at a151ced plus 87f488d's merge): approve, no must-fix. It checked that the check still catches everything PR #11's did, with scratch specs, and that each fix's test fails without it. Five follow-ups for later:

  • F1: the quit marker is logged after two other before-quit listeners, so a hang in one of them is reported as a quit that never reached main.
  • F2: the 35 s timer keeps running after the marker. A quit whose marker comes more than 20 s after app.close() can be killed before its 15 s are up, and the kill names the wrong cause.
  • F3: an app that quits on its own is checked by that worker's next launchOrbit(), which may be the next test or none.
  • F4: sample can't run here; the reviewer is moderately sure it works.
  • F5: after the kill, await closed has no time limit, as in PR Main-process exceptions no longer block quitting (D28): the check, the strip's fade timer, two test races #11.

🤖 Generated with Claude Code


Note

Medium Risk
Changes main-process error handling and tab load paths during restore/teardown; e2e harness behavior only in tests but alters how quit failures are diagnosed in CI.

Overview
Tightens the e2e quit/health harness (D28) and fixes a tab-runtime bug that showed up under the stricter checks.

The 15 s stuck-quit timer now starts when main logs [orbit] quit requested in before-quit (e2e only), not when Playwright calls app.close(). If quit never reaches main, the harness kills after 35 s and reports whether the process had the quit for 15 s or never saw it. Self-quitting apps (last window closed) are queued and verified on the next launchOrbit() or their own app.close(). On macOS, a stuck kill includes a one-second sample of the main thread instead of unknown.

Main now logs unhandled promise rejections with the same [orbit] uncaught exception: marker as sync throws, so e2e fails those tests too. TabRuntime.load no-ops when WebContents is already destroyed, avoiding an unhandled rejection when a tab is closed mid–history restore.

New TB-22 e2e cases cover rejection logging and mid-restore close; docs/ARCHITECTURE.md §9 documents the updated quit rules.

Reviewed by Cursor Bugbot for commit 87f488d. Bugbot is set up for automated code reviews on this repo. Configure here.

…cks apps that quit on their own, and samples a stuck main thread on macOS

Follow-ups F1, F2 and F4 from PR #11's review (D28).

- F1: Playwright does work of its own before it asks Orbit to quit
  (5 s after a failed test, measured by the reviewer), and the 15 s
  clock used to include it. In e2e mode main now logs
  "[orbit] quit requested" in before-quit; Orbit gets 15 s from that
  line, or 35 s from app.close() if the line never comes. The failure
  says which.
- F2: an app that quits on its own (its last window closed) had no
  app.close() to check it. It is now remembered when it closes, and
  the next launchOrbit() checks it.
- F4: macOS has no /proc, so a kill said "main thread: unknown". It now
  runs `sample <pid> 1` and reports the main thread's deepest frames,
  with the full sample's path. Not run on macOS yet; any error still
  falls back to "unknown".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 8923aecd-68fc-43f9-9e28-34d743d9cc86)

siyabendoezdemir and others added 2 commits October 10, 2026 03:05
…thing handled in main

Electron only prints a warning for an unhandled rejection in the main process,
so the D28 check never saw one. Main now logs them with the same marker as an
uncaught exception, so the check fails the test that caused it.

The new TB-22 test fails without the logging (received "") and passes with it.

PR #11 review F3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… rejection behind

Waking a tab restores its history. If the tab closed before the restore
finished, restore's catch loaded the page into contents already destroyed:
loadURL threw "Object has been destroyed", and the rejection went
unhandled (archive review F8, seen after archive's restore tests at quit).
load() now does nothing for destroyed contents.

The new TB-22 test fails without the fix, now that the quit check sees
rejections (Received: "[orbit] uncaught exception: (a promise rejection
nothing handled) TypeError: Object has been destroyed"), and passes 3/3
with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 1dbf0c3d-6134-43ea-aee4-3ac75069d9fb)

One conflict, tests/e2e/helpers.ts: media's launchOrbit options
(reducedMotion and args) with PR #13's check of an app that quit on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
One conflict, tests/e2e/helpers.ts, resolved as in 87f488d: media's
launchOrbit options (reducedMotion and args) with PR #13's check of an
app that quit on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@siyabendoezdemir
siyabendoezdemir changed the base branch from lead/quit to main October 10, 2026 04:58
@cursor

cursor Bot commented Oct 10, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: 668c0111-db91-4488-8b1c-d28098d66aeb)

@siyabendoezdemir
siyabendoezdemir merged commit a8478c4 into main Oct 10, 2026
1 check passed
siyabendoezdemir added a commit that referenced this pull request Oct 10, 2026
docs/ARCHITECTURE.md §9: main's E2E bullet (the quit clock now starts when the
quit reaches main) with this branch's merge-gate bullet, which now also gives
the gate's exact e2e command (lead-scope review F2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant