Skip to content

e2e: cross-OS supervised-daemon reboot survival (real VMs, recorded) - #1013

Merged
RhysSullivan merged 6 commits into
mainfrom
e2e/vm-reboot-targets
Jun 14, 2026
Merged

e2e: cross-OS supervised-daemon reboot survival (real VMs, recorded)#1013
RhysSullivan merged 6 commits into
mainfrom
e2e/vm-reboot-targets

Conversation

@RhysSullivan

Copy link
Copy Markdown
Collaborator

What

Codifies the supervised local daemon's reboot survival as e2e scenarios that run against real virtual machines — replacing a by-hand bash harness. The daemon (added by the PRs below this in the stack) is installed as an OS service (launchd / systemd / Task Scheduler); these tests prove it auto-starts after a genuine machine reboot and that integration data survives.

How it's built

  • VM substrate (e2e/src/vm/) — provision a guest, drive it over SSH, reboot the OS for real, tear it down. A tart provider covers macOS + Linux; the SSH forward reconnects across the reboot.
  • cli target (e2e/targets/cli.ts, one project per guest OS) — globalsetup builds the guest's executor binary, executor service installs the daemon, and tunnels its loopback port to target.baseUrl. restart() is a real guest reboot, so the existing restart-persistence scenario runs unchanged and proves the boot-time auto-start path.
  • --target flag on the CLI build so the harness can compile a specific platform's binary.
  • Two recorded films for press-play evidence in the viewer matrix:
    • e2e/cli/service-lifecycle.test.ts — register an integration → reboot → survived, where the on-screen spinner runs for the actual reboot (terminal cast).
    • e2e/desktop/supervised-attach.test.ts — the desktop app attaching to a running supervised daemon instead of spawning its own sidecar (proven by the manifest pid staying put).

Verified

  • vitest run --project cli-macos and --project cli-linuxgreen, each doing build → provision → install → addSpec → real reboot → assert survived → clean teardown.
  • The recorded reboot film produces a watchable terminal cast.
  • The desktop film's daemon half is verified cold (EXECUTOR_READY + manifest kind: cli-daemon + 401).

Coverage notes (honest)

  • These projects need a VM (or, for the desktop film, a GUI display), so they are not in the default bun run test chain — same as the existing desktop and selfhost-docker projects. Run them explicitly on a host with tart / a display.
  • cli-windows is wired but pending an ec2.ts provider (the Windows reboot path was proven by hand; porting it into the substrate is the remaining piece).
  • The desktop film drives Electron via Playwright, which needs a real GUI session (macOS has no headless display); run it from a desktop session to capture session.mp4.

Stacked on top of the supervised-daemon PRs.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 6f4f991 Commit Preview URL

Branch Preview URL
Jun 14 2026, 07:13 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 14, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 6f4f991 Jun 14 2026, 07:14 AM

@github-actions

github-actions Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@pkg-pr-new

pkg-pr-new Bot commented Jun 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1013

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1013

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1013

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1013

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1013

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1013

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1013

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1013

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1013

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1013

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1013

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1013

executor

npm i https://pkg.pr.new/executor@1013

commit: 6f4f991

@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR codifies supervised-daemon reboot survival as automated e2e scenarios against real VMs, replacing a hand-driven bash harness. The VM substrate (tart for macOS/Linux, EC2 for Windows) provisions guests, installs the daemon as an OS service, and makes restart() a genuine machine reboot — so restart-persistence proves the boot-time auto-start path rather than just a process kick.

  • VM substrate (e2e/src/vm/): tart and EC2 providers implement a common VmHandle interface; reboot correctness is gated on boot-time proofs (boot_id on Linux, LastBootUpTime on Windows) rather than SSH reachability alone; reconnecting SSH tunnels forward the daemon's loopback port to the test host.
  • --target flag on apps/cli/src/build.ts: resolveTargetsFromEnv is now shared between the new --target CLI arg and the existing EXECUTOR_PREVIEW_TARGETS env path; error messages were generalized, losing the env-var hint for the preview CI case.
  • Two filmed scenarios: e2e/cli/service-lifecycle.test.ts (terminal cast of a real reboot) and e2e/desktop/supervised-attach.test.ts (desktop attach vs. sidecar-spawn proof via manifest pid).

Confidence Score: 5/5

Safe to merge — all changes are additive test infrastructure with no modifications to production application logic; the projects are excluded from the default test chain.

The new VM substrate and test scenarios are well-isolated from production paths. The build.ts change is narrow and the --target flag integrates cleanly with the existing binary-build path. No data-path or auth changes.

e2e/setup/cli.globalsetup.ts — the readToken/waitForHttp ordering (flagged in a prior thread) is the most fragile piece of the setup path.

Important Files Changed

Filename Overview
e2e/src/vm/ec2.ts New EC2 provider for Windows/Linux VMs — boot-time proof via LastBootUpTime/boot_id is good; freePort is duplicated from tart.ts and supervised-attach.test.ts (three copies now).
e2e/src/vm/tart.ts New tart provider for macOS/Linux VMs — reconnecting SSH tunnel is well-structured; reboot waits for a new DHCP IP and SSH before returning; freePort is one of three duplicate copies in the codebase.
e2e/setup/cli.globalsetup.ts New CLI globalsetup — readToken is called before waitForHttp confirms the daemon is serving (previously flagged), creating a timing hazard where JSON.parse("") throws a cryptic SyntaxError if auth.json has not been written yet.
e2e/targets/cli.ts CLI target — restart() correctly gates on HTTP down then HTTP up; E2E_CLI_VM_HOST captures the IP at provision time and is not refreshed after reboot (previously flagged).
e2e/cli/service-lifecycle.test.ts New recorded service-lifecycle scenario — cleanup via Effect.ensuring is correct; needs: ["api"] is omitted (previously flagged); assertions are value-based.
e2e/desktop/supervised-attach.test.ts New desktop film — daemon cleanup in finally block is correct; freePort duplicate (previously flagged); manifest-pid proof of attachment is clear.
apps/cli/src/build.ts Adds --target flag and refactors resolveTargetsFromEnv for shared use; the generalized error message loses the EXECUTOR_PREVIEW_TARGETS hint the preview CI path relied on.
e2e/setup/boot.ts Adds waitForHttpDown — logic is correct; the down-gate prevents the reconnecting tunnel from masking a reboot that never happened.
e2e/vitest.config.ts Adds cli-macos/linux/windows projects; globalSetup is correctly derived from the project() helper by name.
e2e/src/vm/types.ts New VM substrate interfaces — clean, minimal, correctly marks sshKeyPath optional for password-based providers.
e2e/src/vm/build-binary.ts Cross-compiles executor binary via --target flag; verifies the output file exists after build before returning the bin directory.

Sequence Diagram

sequenceDiagram
    participant GS as cli.globalsetup
    participant VM as VmHandle (tart/ec2)
    participant D as OS Daemon
    participant T as SSH Tunnel
    participant W as Test Worker

    GS->>VM: provision()
    VM-->>GS: handle (ip, ssh, tunnel, reboot)
    GS->>VM: ssh(prep + push + postPush)
    GS->>VM: ssh(service install)
    VM->>D: OS registers and starts daemon
    GS->>VM: ssh(readToken) → auth.json
    GS->>VM: tunnel(PORT)
    VM-->>T: spawn reconnecting ssh -L forward
    GS->>T: waitForHttp(baseUrl)
    T-->>GS: HTTP 200
    Note over GS: Publishes env: BASE_URL, AUTH_TOKEN, VM_HOST
    W->>W: cliTarget.restart()
    W->>VM: sshRebootGuest / ec2RebootGuest
    W->>T: waitForHttpDown(baseUrl)
    T-->>W: connection refused (guest rebooting)
    Note over VM: OS boots, service manager starts daemon
    T->>VM: SSH tunnel reconnects
    W->>T: waitForHttp(baseUrl, 240s)
    T-->>W: HTTP 200 (daemon auto-started)
    W->>T: after.integrations.list()
    T->>D: GET /integrations
    D-->>W: slug still present ✓
Loading

Reviews (3): Last reviewed commit: "e2e/desktop + cli tests: bearer auth tok..." | Re-trigger Greptile

Comment thread e2e/targets/cli.ts
Comment thread e2e/cli/service-lifecycle.test.ts
Comment on lines +32 to +40
const freePort = (): Promise<number> =>
new Promise((resolve, reject) => {
const srv = net.createServer();
srv.on("error", reject);
srv.listen(0, "127.0.0.1", () => {
const port = (srv.address() as net.AddressInfo).port;
srv.close(() => resolve(port));
});
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicate freePort implementation

The identical freePort() function already exists verbatim in e2e/src/vm/tart.ts. Both files open a port-0 server, grab the address, close the listener, and return the number. e2e/AGENTS.md directs "extract shared logic only when the shared behavior is real" — here the behaviour is exactly the same. Extracting it to a small shared utility (e.g. e2e/src/vm/types.ts or a dedicated e2e/src/net.ts) would avoid the copy diverging if the timeout or bind-address ever needs updating.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@RhysSullivan
RhysSullivan force-pushed the daemon/desktop-attach branch from 04595d7 to 3203153 Compare June 14, 2026 06:47
@RhysSullivan
RhysSullivan force-pushed the e2e/vm-reboot-targets branch from 9a0841a to a030e91 Compare June 14, 2026 06:59
@RhysSullivan
RhysSullivan force-pushed the daemon/desktop-attach branch from 3203153 to 50c18a9 Compare June 14, 2026 07:05
Comment thread e2e/setup/cli.globalsetup.ts
…rget

Foundation for codifying the supervised-daemon reboot-survival proof as e2e
tests (today it's only a by-hand bash harness). Adds a VM substrate that can
provision a guest, drive it over SSH, REBOOT the OS for real, and tear it down:

- e2e/src/vm/{types,tart,build-binary}.ts — the tart provider (macOS + Linux
  guests on an Apple-Silicon host) with a reconnecting SSH forward that survives
  a guest reboot, plus a helper to compile the guest's `executor` binary.
- apps/cli/src/build.ts: a first-class `--target <name>` flag so the harness can
  build a specific platform's binary (replaces a throwaway env hack).

Validated standalone: provision macOS guest → ssh → real reboot (boottime
changes) → clean discard. Next: the `cli` target wires this into the scenario
suite so restart-persistence runs against a real reboot.
Wires the VM substrate into the scenario suite as a `cli` target (one project
per guest OS: cli-macos / cli-linux / cli-windows). The globalsetup builds the
guest's executor binary, provisions a VM, `executor service install`s the
supervised daemon, and forwards its loopback port over a reconnecting SSH
tunnel — so target.baseUrl works unchanged for the api surface. restart()
reboots the guest OS for REAL and waits for the daemon to auto-start, so the
existing restart-persistence scenario now proves the boot-time auto-start path
(launchd RunAtLoad / systemd linger / Task Scheduler AtStartup), not a process
restart.

Because globalsetup (main process) and restart() (test worker) are different
processes, restart() re-derives the guest address from env and reboots
statelessly, mirroring how selfhost-docker's restart re-derives container ops.

Verified: `vitest run --project cli-macos` boots a macOS guest, installs the
daemon, addSpec's an integration, reboots the guest, and asserts the data
survived — green, with a run-record in the matrix. cli-linux reuses the tart
provider; cli-windows awaits the ec2 provider.
A watchable companion to restart-persistence: drives the same register →
reboot → survived flow through the chat theater so the on-screen tool spinner
runs for the ACTUAL guest reboot. Produces a terminal.cast the viewer plays in
the matrix — press-play evidence instead of trusting a green check. Runs
against the cli-* VM targets; verified on cli-macos (357-event cast).
… a sidecar)

Films the desktop ATTACHING to an already-running OS-supervised daemon instead
of spawning its own sidecar: starts a real cli-daemon (the desktop sidecar
server in EXECUTOR_SUPERVISED mode → kind "cli-daemon") against a throwaway
HOME, launches Electron pointed at the same HOME, and proves the attach — the
server manifest still names OUR daemon's pid (a spawned sidecar would be a new
pid + kind "desktop-sidecar"). session.mp4 is the artifact.

The daemon half is verified (EXECUTOR_READY + kind cli-daemon + 401). Like the
existing desktop scenario it drives Electron via Playwright, so it needs a GUI
display session to run (the desktop project is already display-gated, not in
the default test chain) — run on a real desktop / CI-with-display to capture
the video.
…or Windows

- cli target authenticates with the daemon's auth.json bearer (was Basic from
  service.key); globalsetup reads the token from the guest and publishes it.
- restart() gates on the daemon going DOWN before polling it back up, so an
  orderly shutdown / reconnecting tunnel can't false-pass a reboot that never
  happened (waitForHttpDown).
- EC2 VmProvider (e2e/src/vm/ec2.ts) + cli-windows globalsetup unblock Windows:
  launch, key-based SSH/PowerShell drive, real boot-time-gated reboot, terminate.
The supervised-attach film passed EXECUTOR_AUTH_PASSWORD, a dead env the
sidecar no longer reads (it reads EXECUTOR_AUTH_TOKEN and otherwise
mints/loads the bearer from auth.json). Pin a real bearer instead. Fix two
stale launchd/wrapper comments that still pointed the secret at service.key.
@RhysSullivan
RhysSullivan force-pushed the e2e/vm-reboot-targets branch from a030e91 to 6f4f991 Compare June 14, 2026 07:12
@RhysSullivan
RhysSullivan changed the base branch from daemon/desktop-attach to main June 14, 2026 07:12
@RhysSullivan
RhysSullivan merged commit 211fa52 into main Jun 14, 2026
14 checks passed
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