From 13eca551f33cc3c45d4aa8e488f76e5e6d852d15 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:30:29 -0700 Subject: [PATCH] e2e: repro stale launchd registration causing 15s dead wait at desktop boot --- .../stale-launchd-registration.test.ts | 885 ++++++++++++++++++ e2e/recordings/stale-launchd-slow-boot.mp4 | Bin 0 -> 39879 bytes 2 files changed, 885 insertions(+) create mode 100644 e2e/desktop-packaged/stale-launchd-registration.test.ts create mode 100644 e2e/recordings/stale-launchd-slow-boot.mp4 diff --git a/e2e/desktop-packaged/stale-launchd-registration.test.ts b/e2e/desktop-packaged/stale-launchd-registration.test.ts new file mode 100644 index 000000000..d7f6c3b99 --- /dev/null +++ b/e2e/desktop-packaged/stale-launchd-registration.test.ts @@ -0,0 +1,885 @@ +/* oxlint-disable executor/no-try-catch-or-throw, executor/no-error-constructor, executor/no-promise-reject -- e2e boundary: drives launchd, a packaged Electron process, CDP, and optional ffmpeg recording */ +import { type ChildProcess, execFile, execFileSync, spawn } from "node:child_process"; +import { + chmodSync, + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + statSync, + writeFileSync, +} from "node:fs"; +import { homedir, tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { promisify } from "node:util"; + +import { expect, it } from "@effect/vitest"; +import { Effect } from "effect"; + +import { scenario } from "../src/scenario"; +import { RunDir } from "../src/services"; + +const execFileAsync = promisify(execFile); +const SERVICE_LABEL = "sh.executor.daemon"; +const SCENARIO_NAME = + "Desktop packaged supervised attach: stale launchd registration pins the 15s dead boot wait"; +const RECORDING_FILE = resolve("recordings/stale-launchd-slow-boot.mp4"); +const RECORDING_WIDTH = 1280; +const RECORDING_HEIGHT = 720; +const STALE_PLIST_PORT = 58_251; + +interface PackagedExecutorBridge { + readonly getServerConnection: () => Promise<{ + readonly kind: string; + readonly origin: string; + } | null>; +} + +interface PackagedApp { + readonly child: ChildProcess; + cdp: CdpPage; + readonly debugPort: string; + readonly output: () => string; +} + +interface CdpResponse { + readonly id: number; + readonly result?: T; + readonly error?: { readonly message?: string; readonly data?: string }; +} + +interface CdpEvaluateResult { + readonly result: { readonly value?: unknown }; + readonly exceptionDetails?: unknown; +} + +interface CdpTarget { + readonly type: string; + readonly url: string; + readonly webSocketDebuggerUrl?: string; +} + +interface ServerManifest { + readonly kind: string; + readonly pid: number; +} + +interface LaunchTiming { + readonly app: PackagedApp; + readonly pageCdpMs: number; +} + +interface BootResult { + readonly pageCdpMs: number; + readonly sidecarReadyMs: number; + readonly connectionKind: string; + readonly origin: string; + readonly manifestKind: string; + readonly manifestPid: number; + readonly matchedLogLine: string | null; +} + +declare global { + interface Window { + readonly executor: PackagedExecutorBridge; + } +} + +class CdpPage { + private nextId = 1; + private readonly pending = new Map< + number, + { + readonly resolve: (value: unknown) => void; + readonly reject: (error: Error) => void; + } + >(); + + private constructor(private readonly socket: WebSocket) { + socket.addEventListener("message", (event) => { + const data = event.data; + if (typeof data !== "string") return; + const message = JSON.parse(data) as CdpResponse; + if (!message.id) return; + const pending = this.pending.get(message.id); + if (!pending) return; + this.pending.delete(message.id); + if (message.error) { + pending.reject(new Error(message.error.message ?? "CDP command failed")); + return; + } + pending.resolve(message.result); + }); + socket.addEventListener("close", () => { + for (const [, pending] of this.pending) { + pending.reject(new Error("CDP socket closed")); + } + this.pending.clear(); + }); + } + + static connect = (url: string): Promise => + new Promise((resolveConnect, rejectConnect) => { + const socket = new WebSocket(url); + const timer = setTimeout(() => { + socket.close(); + rejectConnect(new Error(`Timed out connecting to page CDP target ${url}`)); + }, 30_000); + socket.addEventListener( + "open", + () => { + clearTimeout(timer); + resolveConnect(new CdpPage(socket)); + }, + { once: true }, + ); + socket.addEventListener( + "error", + () => { + clearTimeout(timer); + rejectConnect(new Error(`Failed to connect to page CDP target ${url}`)); + }, + { once: true }, + ); + }); + + command = async (method: string, params: Record = {}): Promise => { + const id = this.nextId; + this.nextId += 1; + const result = new Promise((resolveCommand, rejectCommand) => { + this.pending.set(id, { + resolve: (value) => resolveCommand(value as T), + reject: rejectCommand, + }); + }); + this.socket.send(JSON.stringify({ id, method, params })); + return result; + }; + + evaluate = async (expression: string): Promise => { + const result = await this.command("Runtime.evaluate", { + expression, + awaitPromise: true, + returnByValue: true, + }); + if (result.exceptionDetails) { + throw new Error(`CDP evaluation failed: ${JSON.stringify(result.exceptionDetails)}`); + } + return result.result.value as T; + }; + + waitForText = async (text: string, timeoutMs: number): Promise => { + const deadline = Date.now() + timeoutMs; + const expression = `document.body?.innerText.includes(${JSON.stringify(text)}) ?? false`; + for (;;) { + if (await this.evaluate(expression).catch(() => false)) return; + if (Date.now() >= deadline) throw new Error(`Timed out waiting for text: ${text}`); + await new Promise((resolveWait) => setTimeout(resolveWait, 250)); + } + }; + + setViewport = async (width: number, height: number): Promise => { + await this.command("Emulation.setDeviceMetricsOverride", { + width, + height, + deviceScaleFactor: 1, + mobile: false, + }); + }; + + screenshot = async (path: string): Promise => { + const result = await this.command<{ readonly data: string }>("Page.captureScreenshot", { + format: "png", + fromSurface: true, + }); + writeFileSync(path, Buffer.from(result.data, "base64")); + }; + + close = (): void => { + this.socket.close(); + }; +} + +const appExe = process.env.E2E_DESKTOP_APP_EXE; +const executorBin = process.env.E2E_DESKTOP_EXECUTOR_BIN; + +const macAquaAvailable = (): boolean => { + if (process.platform !== "darwin") return false; + try { + return execFileSync("launchctl", ["managername"], { encoding: "utf8" }).trim() === "Aqua"; + } catch { + return false; + } +}; + +const packagedSingleInstanceAvailable = (): boolean => { + if (process.platform !== "darwin" || !appExe) return true; + try { + const lines = execFileSync("pgrep", ["-fl", "Executor.app/Contents/MacOS/Executor"], { + encoding: "utf8", + }) + .split("\n") + .filter(Boolean); + return !lines.some((line) => !line.includes(appExe)); + } catch { + return true; + } +}; + +const requireBundle = (): { readonly app: string; readonly executor: string } => { + if (!appExe || !executorBin) { + throw new Error( + "E2E_DESKTOP_APP_EXE / E2E_DESKTOP_EXECUTOR_BIN not set, did desktop-packaged.globalsetup run?", + ); + } + return { app: appExe, executor: executorBin }; +}; + +const currentUid = (): number => { + const getuid = (process as { readonly getuid?: () => number }).getuid; + return typeof getuid === "function" ? getuid.call(process) : 0; +}; + +const serviceTarget = (): string => `gui/${currentUid()}/${SERVICE_LABEL}`; +const launchAgentPath = (): string => + join(homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`); +const isolatedDesktopSettingsDir = (home: string): string => + join(home, ".executor-desktop-settings"); + +const packagedAppEnv = (home: string): NodeJS.ProcessEnv => ({ + ...process.env, + HOME: home, + EXECUTOR_DESKTOP_SETTINGS_DIR: isolatedDesktopSettingsDir(home), +}); + +const cleanBootEnv = (home: string): NodeJS.ProcessEnv => ({ + ...packagedAppEnv(home), + EXECUTOR_TEST_SKIP_BACKGROUND_SERVICE: "1", + EXECUTOR_TEST_AUTO_CONFIRM_BACKGROUND_SERVICE: "0", +}); + +interface LaunchdServiceSnapshot { + readonly plist: string | null; + readonly wasLoaded: boolean; +} + +const launchctl = async (args: ReadonlyArray): Promise => { + try { + await execFileAsync("launchctl", [...args]); + return true; + } catch { + return false; + } +}; + +const captureLaunchdService = (): LaunchdServiceSnapshot | null => { + if (process.platform !== "darwin") return null; + const path = launchAgentPath(); + const plist = existsSync(path) ? readFileSync(path, "utf8") : null; + let wasLoaded = false; + try { + execFileSync("launchctl", ["print", serviceTarget()], { stdio: "ignore" }); + wasLoaded = true; + } catch { + wasLoaded = false; + } + return { plist, wasLoaded }; +}; + +const restoreLaunchdService = async (snapshot: LaunchdServiceSnapshot | null): Promise => { + if (!snapshot) return; + const target = serviceTarget(); + await launchctl(["bootout", target]); + const path = launchAgentPath(); + if (snapshot.plist === null) { + rmSync(path, { force: true }); + return; + } + mkdirSync(dirname(path), { recursive: true }); + writeFileSync(path, snapshot.plist, { mode: 0o600 }); + chmodSync(path, 0o600); + await launchctl(["enable", target]); + if (snapshot.wasLoaded) { + const bootstrapped = await launchctl(["bootstrap", `gui/${currentUid()}`, path]); + if (bootstrapped) await launchctl(["kickstart", "-k", target]); + } +}; + +const waitForPageWebSocket = async (debugPort: string): Promise => { + const deadline = Date.now() + 120_000; + for (;;) { + const targets = (await fetch(`http://127.0.0.1:${debugPort}/json/list`) + .then((response) => (response.ok ? response.json() : [])) + .catch(() => [])) as ReadonlyArray; + const page = targets.find( + (target) => + target.type === "page" && + target.webSocketDebuggerUrl && + !target.url.startsWith("devtools://"), + ); + if (page?.webSocketDebuggerUrl) return page.webSocketDebuggerUrl; + if (Date.now() >= deadline) { + throw new Error("Timed out waiting for packaged app page CDP target"); + } + await new Promise((resolveWait) => setTimeout(resolveWait, 100)); + } +}; + +const launchPackaged = async ( + env: NodeJS.ProcessEnv, + recorder: BootRecorder | null, +): Promise => { + const { app } = requireBundle(); + const startedAt = Date.now(); + let output = ""; + let settled = false; + const child = spawn(app, ["--remote-debugging-port=0"], { + env, + stdio: ["ignore", "pipe", "pipe"], + }); + recorder?.start(startedAt); + + try { + const browserCdpUrl = await new Promise((resolveLaunch, rejectLaunch) => { + const timer = setTimeout(() => { + rejectLaunch(new Error(`Timed out waiting for packaged app CDP URL\n${output}`)); + }, 120_000); + const settle = (fn: () => void) => { + if (settled) return; + settled = true; + clearTimeout(timer); + fn(); + }; + const collectOutput = (chunk: Buffer) => { + const text = chunk.toString(); + output = (output + text).slice(-16_384); + const match = output.match(/DevTools listening on (ws:\/\/[^\s]+)/); + if (match) settle(() => resolveLaunch(match[1])); + }; + child.stdout?.on("data", collectOutput); + child.stderr?.on("data", collectOutput); + child.once("error", (error) => settle(() => rejectLaunch(error))); + child.once("exit", (code, signal) => + settle(() => + rejectLaunch( + new Error(`Packaged app exited before CDP (code=${code} signal=${signal})\n${output}`), + ), + ), + ); + }); + + const debugPort = new URL(browserCdpUrl).port; + const pageCdpUrl = await waitForPageWebSocket(debugPort); + const cdp = await CdpPage.connect(pageCdpUrl); + await cdp.command("Runtime.enable"); + await cdp.command("Page.enable"); + await cdp.setViewport(RECORDING_WIDTH, RECORDING_HEIGHT); + recorder?.attachPage(cdp); + return { + app: { child, cdp, debugPort, output: () => output }, + pageCdpMs: Date.now() - startedAt, + }; + } catch (error) { + await stopProcess(child); + throw error; + } +}; + +const stopProcess = async (child: ChildProcess | undefined): Promise => { + if (!child || child.exitCode !== null || child.signalCode !== null) return; + await new Promise((resolveStop) => { + const timeout = setTimeout(() => { + child.kill("SIGKILL"); + resolveStop(); + }, 5_000); + child.once("exit", () => { + clearTimeout(timeout); + resolveStop(); + }); + child.kill("SIGTERM"); + }); +}; + +const closePackaged = async (app: PackagedApp | undefined): Promise => { + app?.cdp.close(); + await stopProcess(app?.child); +}; + +const xmlEscape = (value: string): string => + value + .replaceAll("&", "&") + .replaceAll('"', """) + .replaceAll(">", ">") + .replaceAll("<", "<"); + +const writeStaleLaunchAgentPlist = (home: string): void => { + const { executor } = requireBundle(); + const dataDir = join(home, ".executor"); + const logs = join(dataDir, "logs"); + const plistDir = join(home, "Library", "LaunchAgents"); + mkdirSync(plistDir, { recursive: true }); + mkdirSync(logs, { recursive: true }); + mkdirSync(dataDir, { recursive: true }); + + const programArguments = [ + executor, + "daemon", + "run", + "--foreground", + "--port", + String(STALE_PLIST_PORT), + "--hostname", + "127.0.0.1", + ]; + const programArgs = programArguments + .map((arg) => ` ${xmlEscape(arg)}`) + .join("\n"); + const plist = ` + + + + Label + ${SERVICE_LABEL} + ProgramArguments + +${programArgs} + + EnvironmentVariables + + EXECUTOR_SUPERVISED + 1 + EXECUTOR_DATA_DIR + ${xmlEscape(dataDir)} + EXECUTOR_SCOPE_DIR + ${xmlEscape(dataDir)} + EXECUTOR_CLIENT + desktop + + RunAtLoad + + KeepAlive + + SuccessfulExit + + + ProcessType + Background + WorkingDirectory + ${xmlEscape(dataDir)} + StandardOutPath + ${xmlEscape(join(logs, "daemon.log"))} + StandardErrorPath + ${xmlEscape(join(logs, "daemon.error.log"))} + + +`; + writeFileSync(join(plistDir, `${SERVICE_LABEL}.plist`), plist, { mode: 0o600 }); +}; + +const readServerManifest = (home: string): ServerManifest => { + const manifestPath = join(home, ".executor", "server-control", "server.json"); + return JSON.parse(readFileSync(manifestPath, "utf8")) as ServerManifest; +}; + +const readKickstartFailureLine = (home: string): string | null => { + const logPath = join(home, "Library", "Logs", "Executor", "main.log"); + if (!existsSync(logPath)) return null; + return ( + readFileSync(logPath, "utf8") + .split("\n") + .find( + (line) => + line.includes("launchctl kickstart failed (exit 113)") && + line.includes('Could not find service "sh.executor.daemon"'), + ) ?? null + ); +}; + +const waitForKickstartFailureLine = async (home: string): Promise => { + const deadline = Date.now() + 30_000; + for (;;) { + const line = readKickstartFailureLine(home); + if (line) return line; + if (Date.now() >= deadline) { + const logPath = join(home, "Library", "Logs", "Executor", "main.log"); + const tail = existsSync(logPath) + ? readFileSync(logPath, "utf8").split("\n").slice(-20).join("\n") + : ""; + throw new Error(`Timed out waiting for kickstart failure log line\n${tail}`); + } + await new Promise((resolveWait) => setTimeout(resolveWait, 250)); + } +}; + +const bootPackagedApp = async (input: { + readonly home: string; + readonly env: NodeJS.ProcessEnv; + readonly recorder: BootRecorder | null; + readonly expectKickstartFailure: boolean; +}): Promise => { + let app: PackagedApp | undefined; + const startedAt = Date.now(); + try { + const launched = await launchPackaged(input.env, input.recorder); + app = launched.app; + const page = app.cdp; + await page.waitForText("Settings", 120_000); + const sidecarReadyMs = Date.now() - startedAt; + await input.recorder?.captureReadyFrame(); + await input.recorder?.captureReadyFrame(); + const connection = await page.evaluate<{ + readonly kind: string; + readonly origin: string; + } | null>("window.executor.getServerConnection()"); + expect(connection, "the app eventually exposes a server connection").not.toBeNull(); + const manifest = readServerManifest(input.home); + const matchedLogLine = input.expectKickstartFailure + ? await waitForKickstartFailureLine(input.home) + : readKickstartFailureLine(input.home); + return { + pageCdpMs: launched.pageCdpMs, + sidecarReadyMs, + connectionKind: connection!.kind, + origin: connection!.origin, + manifestKind: manifest.kind, + manifestPid: manifest.pid, + matchedLogLine, + }; + } finally { + await input.recorder?.stop(); + await closePackaged(app); + } +}; + +const commandAvailable = (command: string): boolean => { + try { + execFileSync("which", [command], { stdio: "ignore" }); + return true; + } catch { + return false; + } +}; + +const emitEvidence = (line: string): void => { + execFileSync("printf", ["%s\n", line], { stdio: "inherit" }); +}; + +const FONT: Record = { + " ": ["00000", "00000", "00000", "00000", "00000", "00000", "00000"], + ".": ["00000", "00000", "00000", "00000", "00000", "01100", "01100"], + "0": ["11110", "10010", "10010", "10010", "10010", "10010", "11110"], + "1": ["00100", "01100", "00100", "00100", "00100", "00100", "01110"], + "2": ["11110", "00010", "00010", "11110", "10000", "10000", "11110"], + "3": ["11110", "00010", "00010", "01110", "00010", "00010", "11110"], + "4": ["10010", "10010", "10010", "11110", "00010", "00010", "00010"], + "5": ["11110", "10000", "10000", "11110", "00010", "00010", "11110"], + "6": ["11110", "10000", "10000", "11110", "10010", "10010", "11110"], + "7": ["11110", "00010", "00010", "00100", "00100", "01000", "01000"], + "8": ["11110", "10010", "10010", "11110", "10010", "10010", "11110"], + "9": ["11110", "10010", "10010", "11110", "00010", "00010", "11110"], + A: ["01110", "10001", "10001", "11111", "10001", "10001", "10001"], + B: ["11110", "10001", "10001", "11110", "10001", "10001", "11110"], + C: ["01111", "10000", "10000", "10000", "10000", "10000", "01111"], + D: ["11110", "10001", "10001", "10001", "10001", "10001", "11110"], + E: ["11111", "10000", "10000", "11110", "10000", "10000", "11111"], + F: ["11111", "10000", "10000", "11110", "10000", "10000", "10000"], + G: ["01111", "10000", "10000", "10011", "10001", "10001", "01111"], + H: ["10001", "10001", "10001", "11111", "10001", "10001", "10001"], + I: ["11111", "00100", "00100", "00100", "00100", "00100", "11111"], + J: ["00111", "00010", "00010", "00010", "10010", "10010", "01100"], + K: ["10001", "10010", "10100", "11000", "10100", "10010", "10001"], + L: ["10000", "10000", "10000", "10000", "10000", "10000", "11111"], + M: ["10001", "11011", "10101", "10101", "10001", "10001", "10001"], + N: ["10001", "11001", "10101", "10011", "10001", "10001", "10001"], + O: ["01110", "10001", "10001", "10001", "10001", "10001", "01110"], + P: ["11110", "10001", "10001", "11110", "10000", "10000", "10000"], + Q: ["01110", "10001", "10001", "10001", "10101", "10010", "01101"], + R: ["11110", "10001", "10001", "11110", "10100", "10010", "10001"], + S: ["01111", "10000", "10000", "01110", "00001", "00001", "11110"], + T: ["11111", "00100", "00100", "00100", "00100", "00100", "00100"], + U: ["10001", "10001", "10001", "10001", "10001", "10001", "01110"], + V: ["10001", "10001", "10001", "10001", "10001", "01010", "00100"], + W: ["10001", "10001", "10001", "10101", "10101", "10101", "01010"], + X: ["10001", "10001", "01010", "00100", "01010", "10001", "10001"], + Y: ["10001", "10001", "01010", "00100", "00100", "00100", "00100"], + Z: ["11111", "00001", "00010", "00100", "01000", "10000", "11111"], +}; + +const putPixel = (pixels: Buffer, width: number, x: number, y: number, rgb: readonly number[]) => { + const index = (y * width + x) * 3; + pixels[index] = rgb[0]; + pixels[index + 1] = rgb[1]; + pixels[index + 2] = rgb[2]; +}; + +const fillRect = ( + pixels: Buffer, + width: number, + x: number, + y: number, + rectWidth: number, + rectHeight: number, + rgb: readonly number[], +): void => { + for (let yy = y; yy < y + rectHeight; yy += 1) { + for (let xx = x; xx < x + rectWidth; xx += 1) { + putPixel(pixels, width, xx, yy, rgb); + } + } +}; + +const writePpm = (path: string, width: number, height: number, pixels: Buffer): void => { + writeFileSync(path, Buffer.concat([Buffer.from(`P6\n${width} ${height}\n255\n`), pixels])); +}; + +const makeLabelPpm = (path: string, label: string, height: number): void => { + const pixels = Buffer.alloc(RECORDING_WIDTH * height * 3); + fillRect(pixels, RECORDING_WIDTH, 0, 0, RECORDING_WIDTH, height, [17, 18, 23]); + fillRect(pixels, RECORDING_WIDTH, 0, 0, RECORDING_WIDTH, 110, [0, 0, 0]); + const scale = 6; + let x = 34; + const y = 34; + for (const char of label.toUpperCase()) { + const glyph = FONT[char] ?? FONT[" "]; + for (let row = 0; row < glyph.length; row += 1) { + for (let col = 0; col < glyph[row].length; col += 1) { + if (glyph[row][col] === "1") { + fillRect( + pixels, + RECORDING_WIDTH, + x + col * scale, + y + row * scale, + scale, + scale, + [255, 255, 255], + ); + } + } + } + x += 6 * scale; + if (x > RECORDING_WIDTH - 40) break; + } + writePpm(path, RECORDING_WIDTH, height, pixels); +}; + +class BootRecorder { + private frame = 0; + private page: CdpPage | null = null; + private startedAt = 0; + private timer: ReturnType | null = null; + private queue: Promise = Promise.resolve(); + private enabled: boolean; + + constructor( + private readonly frameDir: string, + private readonly outputPath: string, + ) { + this.enabled = process.env.E2E_RECORD === "1" && commandAvailable("ffmpeg"); + } + + static create = (runDir: string): BootRecorder | null => { + if (process.env.E2E_RECORD !== "1") return null; + const frameDir = join(runDir, "stale-launchd-slow-boot-frames"); + rmSync(frameDir, { recursive: true, force: true }); + mkdirSync(frameDir, { recursive: true }); + mkdirSync(dirname(RECORDING_FILE), { recursive: true }); + rmSync(RECORDING_FILE, { force: true }); + return new BootRecorder(frameDir, RECORDING_FILE); + }; + + start = (startedAt: number): void => { + this.startedAt = startedAt; + if (!this.enabled) return; + this.enqueueFrame("waiting for Electron window", null); + this.timer = setInterval(() => { + this.enqueueFrame( + this.page ? "app window reachable" : "waiting for Electron window", + this.page, + ); + }, 1_000); + }; + + attachPage = (page: CdpPage): void => { + this.page = page; + this.enqueueFrame("app window reachable", page); + }; + + captureReadyFrame = async (): Promise => { + if (!this.enabled) return; + this.enqueueFrame("managed sidecar ready", this.page); + await this.queue; + }; + + stop = async (): Promise => { + if (this.timer) { + clearInterval(this.timer); + this.timer = null; + } + if (!this.enabled) return; + await this.queue; + if (this.frame > 0) { + execFileSync( + "ffmpeg", + [ + "-y", + "-framerate", + "1", + "-i", + join(this.frameDir, "frame-%04d.ppm"), + "-pix_fmt", + "yuv420p", + "-movflags", + "+faststart", + this.outputPath, + ], + { stdio: "ignore" }, + ); + } + }; + + recordingStats = (): { + readonly path: string; + readonly size: number; + readonly frames: number; + } | null => { + if (!existsSync(this.outputPath)) return null; + return { path: this.outputPath, size: statSync(this.outputPath).size, frames: this.frame }; + }; + + private enqueueFrame = (state: string, page: CdpPage | null): void => { + if (!this.enabled) return; + this.queue = this.queue + .then(() => this.writeFrame(state, page)) + .catch((error) => { + this.enabled = false; + console.warn(`[stale-launchd-repro] recording disabled after frame error: ${error}`); + }); + }; + + private writeFrame = async (state: string, page: CdpPage | null): Promise => { + this.frame += 1; + const elapsedSeconds = (Date.now() - this.startedAt) / 1000; + const output = join(this.frameDir, `frame-${String(this.frame).padStart(4, "0")}.ppm`); + const label = `${elapsedSeconds.toFixed(1)}s ${state}`; + if (!page) { + makeLabelPpm(output, label, RECORDING_HEIGHT); + return; + } + const raw = join(this.frameDir, `raw-${String(this.frame).padStart(4, "0")}.png`); + const labelFrame = join(this.frameDir, `label-${String(this.frame).padStart(4, "0")}.ppm`); + await page.screenshot(raw); + makeLabelPpm(labelFrame, label, 110); + execFileSync( + "ffmpeg", + [ + "-y", + "-i", + raw, + "-i", + labelFrame, + "-filter_complex", + `[0:v]scale=${RECORDING_WIDTH}:${RECORDING_HEIGHT}:force_original_aspect_ratio=decrease,pad=${RECORDING_WIDTH}:${RECORDING_HEIGHT}:(ow-iw)/2:(oh-ih)/2:color=black[base];[base][1:v]overlay=0:0`, + "-frames:v", + "1", + output, + ], + { stdio: "ignore" }, + ); + rmSync(raw, { force: true }); + rmSync(labelFrame, { force: true }); + }; +} + +const run = async (runDir: string): Promise => { + const cleanHome = mkdtempSync(join(tmpdir(), "executor-pkg-clean-boot-control-")); + const staleHome = mkdtempSync(join(tmpdir(), "executor-pkg-stale-launchd-")); + const launchdSnapshot = captureLaunchdService(); + const staleRecorder = BootRecorder.create(runDir); + + try { + const clean = await bootPackagedApp({ + home: cleanHome, + env: cleanBootEnv(cleanHome), + recorder: null, + expectKickstartFailure: false, + }); + + writeStaleLaunchAgentPlist(staleHome); + await launchctl(["bootout", serviceTarget()]); + const stale = await bootPackagedApp({ + home: staleHome, + env: packagedAppEnv(staleHome), + recorder: staleRecorder, + expectKickstartFailure: true, + }); + + /* + * Regression pin for the current broken behavior: + * a stale plist makes `service status` report registered even though launchd + * cannot find the label. The packaged app logs the kickstart failure, then + * still pays the full 15s wait before falling back to managed-spawn. + * + * When the fix lands, invert the >=14s stale assertions. The desired + * behavior is a fast-fail to managed-spawn, close to the clean control. + */ + expect(stale.matchedLogLine, "stale boot logs the launchd kickstart failure").toContain( + "launchctl kickstart failed (exit 113)", + ); + expect(stale.matchedLogLine, "stale boot says launchd cannot find the label").toContain( + 'Could not find service "sh.executor.daemon"', + ); + expect( + stale.pageCdpMs, + "window/CDP is not reachable until the dead wait is paid", + ).toBeGreaterThanOrEqual(14_000); + expect( + stale.sidecarReadyMs, + "managed sidecar readiness is delayed by the dead wait", + ).toBeGreaterThanOrEqual(14_000); + expect(stale.connectionKind, "the app eventually recovers through managed-spawn").toBe( + "desktop-sidecar", + ); + expect( + new URL(stale.origin).port, + "managed recovery must not be a launchd attach to the stale plist port", + ).not.toBe(String(STALE_PLIST_PORT)); + expect( + clean.sidecarReadyMs, + "the clean control skips supervision and boots quickly", + ).toBeLessThan(10_000); + expect( + stale.sidecarReadyMs, + "the stale launchd path is dramatically slower than the clean control", + ).toBeGreaterThan(clean.sidecarReadyMs + 5_000); + + const recording = staleRecorder?.recordingStats(); + emitEvidence( + `[stale-launchd-repro] clean=${clean.sidecarReadyMs}ms stale=${stale.sidecarReadyMs}ms stalePageCdp=${stale.pageCdpMs}ms cleanOrigin=${clean.origin} staleOrigin=${stale.origin} staleConnectionKind=${stale.connectionKind} staleManifestKind=${stale.manifestKind} stalePid=${stale.manifestPid}`, + ); + emitEvidence(`[stale-launchd-repro] matchedLogLine=${stale.matchedLogLine}`); + if (recording) { + emitEvidence( + `[stale-launchd-repro] recording=${recording.path} frames=${recording.frames} size=${recording.size}`, + ); + } + } finally { + await restoreLaunchdService(launchdSnapshot); + rmSync(cleanHome, { recursive: true, force: true }); + rmSync(staleHome, { recursive: true, force: true }); + } +}; + +if (!macAquaAvailable() || !packagedSingleInstanceAvailable()) { + it.skip(`${SCENARIO_NAME} (needs macOS Aqua and no already-running Executor.app)`, () => {}); +} else { + scenario( + SCENARIO_NAME, + { timeout: 360_000 }, + Effect.gen(function* () { + requireBundle(); + const runDir = yield* RunDir; + yield* Effect.promise(() => run(runDir)); + }), + ); +} diff --git a/e2e/recordings/stale-launchd-slow-boot.mp4 b/e2e/recordings/stale-launchd-slow-boot.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..15c25f91b203bca18243c4ca2c6b240899c6a59c GIT binary patch literal 39879 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 AN&o-= literal 0 HcmV?d00001