Files
openwork/apps/desktop/electron/nuke.test.mjs
T

869 lines
34 KiB
JavaScript

import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { test } from "node:test";
import {
buildNukeWorkerNukeInput,
buildNukeWorkerPayload,
buildNukeManifest,
executeNukeFreshStart,
runPendingNukeCleanup,
sanitizeDesktopBootstrapConfig,
sanitizeDesktopBootstrapFiles,
scheduleNukeCleanupWorker,
} from "./nuke.mjs";
import { runNukeCleanupWorker } from "./nuke-worker.mjs";
const LEGACY_ORCHESTRATOR_DIR_NAME = ["openwork", "orchestrator"].join("-");
function legacyOrchestratorPath(home) {
return path.join(home, ".openwork", LEGACY_ORCHESTRATOR_DIR_NAME);
}
async function exists(targetPath) {
try {
await readFile(targetPath);
return true;
} catch {
return false;
}
}
async function withTempDir(fn) {
const root = await mkdtemp(path.join(tmpdir(), "openwork-nuke-test-"));
try {
await fn(root);
} finally {
await rm(root, { recursive: true, force: true });
}
}
function pendingNukeInput(root) {
return {
env: { XDG_CONFIG_HOME: path.join(root, "xdg") },
homedir: path.join(root, "home"),
platform: "darwin",
userDataPath: path.join(root, "userData"),
};
}
function pendingNukePath(root) {
return path.join(root, "xdg", "openwork", ".nuke-pending.json");
}
async function writePendingNuke(root, pending) {
const pendingPath = pendingNukePath(root);
await mkdir(path.dirname(pendingPath), { recursive: true });
await writeFile(pendingPath, `${JSON.stringify(pending, null, 2)}\n`, "utf8");
return pendingPath;
}
async function readJson(targetPath) {
return JSON.parse(await readFile(targetPath, "utf8"));
}
function fakeSession() {
const session = {
clearStorageData: async () => {},
flushStorageData: () => {},
};
return {
defaultSession: session,
fromPartition: () => session,
};
}
function fakeRuntimeManager() {
return {
dispose: async () => {},
prepareFreshRuntime: async () => {},
sandboxCleanupOpenworkContainers: async () => ({ candidates: [], removed: [], errors: [] }),
};
}
function fakeUiControlServer() {
return { stop: async () => {} };
}
function fakeApp(userDataPath) {
return {
isPackaged: true,
relaunchCount: 0,
quitCount: 0,
getPath(name) {
if (name === "userData") return userDataPath;
throw new Error(`Unexpected app path ${name}`);
},
relaunch() {
this.relaunchCount += 1;
},
quit() {
this.quitCount += 1;
},
};
}
async function tinyDelay(ms = 140) {
await new Promise((resolve) => setTimeout(resolve, ms));
}
test("buildNukeManifest includes default macOS state roots and preserves bootstrap", () => {
const home = "/Users/alice";
const userDataPath = "/Users/alice/Library/Application Support/com.differentai.openwork";
const manifest = buildNukeManifest({ env: {}, homedir: home, platform: "darwin", userDataPath });
assert.equal(manifest.bootstrapPath, "/Users/alice/.config/openwork/desktop-bootstrap.json");
assert.equal(manifest.preserveBootstrapPath, "/Users/alice/.config/openwork/desktop-bootstrap.json");
assert.deepEqual(manifest.partitions, ["default", "persist:openwork-browser"]);
assert.ok(manifest.deletePaths.includes(userDataPath));
assert.ok(manifest.deletePaths.includes("/Users/alice/.config/openwork/server.json"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.config/openwork/runtime.sqlite"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.config/openwork/runtime.sqlite-wal"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.config/openwork/runtime.sqlite-shm"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.config/openwork/runtime-opencode-config.json"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.config/openwork/engine-instances.json"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.config/openwork/tokens.json"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.config/openwork/env.json"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.local/share/opencode"));
assert.ok(manifest.deletePaths.includes("/Users/alice/Library/Application Support/opencode"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.config/opencode"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.cache/opencode"));
assert.ok(manifest.deletePaths.includes(legacyOrchestratorPath(home)));
assert.ok(!manifest.deletePaths.includes("/Users/alice/.opencode/bin"));
assert.ok(!manifest.deletePaths.includes("/Users/alice/project/.opencode"));
});
test("buildNukeManifest wipes session state, server audit data, and workspace registries", () => {
const home = "/Users/alice";
const userDataPath = "/Users/alice/Library/Application Support/com.differentai.openwork";
const manifest = buildNukeManifest({
env: {},
homedir: home,
platform: "darwin",
userDataPath,
workspacePaths: ["/Users/alice/project", "/Users/alice/other"],
});
assert.ok(manifest.deletePaths.includes("/Users/alice/.local/state/opencode"));
assert.ok(manifest.deletePaths.includes("/Users/alice/.openwork/openwork-server"));
assert.ok(manifest.deletePaths.includes(`${userDataPath}/openwork-workspaces.json`));
assert.ok(manifest.deletePaths.includes(`${userDataPath}/openwork-server-tokens.json`));
assert.ok(manifest.deletePaths.includes(`${userDataPath}/openwork-server-state.json`));
assert.ok(manifest.deletePaths.includes("/Users/alice/project/.opencode/openwork"));
assert.ok(manifest.deletePaths.includes("/Users/alice/project/.opencode/openwork.json"));
assert.ok(manifest.deletePaths.includes("/Users/alice/other/.opencode/openwork"));
assert.ok(!manifest.deletePaths.includes("/Users/alice/project/.opencode"));
assert.ok(!manifest.deletePaths.includes("/Users/alice/project"));
});
test("buildNukeManifest can include the bootstrap file in the wipe", () => {
const bootstrapPath = "/Users/alice/.config/openwork/desktop-bootstrap.json";
const manifest = buildNukeManifest({
env: {},
homedir: "/Users/alice",
platform: "darwin",
preserveBootstrap: false,
userDataPath: "/Users/alice/Library/Application Support/com.differentai.openwork",
});
assert.equal(manifest.bootstrapPath, bootstrapPath);
assert.equal(manifest.preserveBootstrapPath, null);
assert.ok(manifest.deletePaths.includes(bootstrapPath));
});
test("buildNukeManifest includes default Linux state roots", () => {
const manifest = buildNukeManifest({
env: {},
homedir: "/home/alice",
platform: "linux",
userDataPath: "/home/alice/.config/com.differentai.openwork",
});
assert.equal(manifest.preserveBootstrapPath, "/home/alice/.config/openwork/desktop-bootstrap.json");
assert.ok(manifest.deletePaths.includes("/home/alice/.config/com.differentai.openwork"));
assert.ok(manifest.deletePaths.includes("/home/alice/.local/share/opencode"));
assert.ok(manifest.deletePaths.includes("/home/alice/.config/opencode"));
assert.ok(manifest.deletePaths.includes("/home/alice/.cache/opencode"));
assert.ok(!manifest.deletePaths.some((targetPath) => targetPath.includes("Library/Application Support/opencode")));
});
test("buildNukeManifest includes Windows path shapes", () => {
const env = {
LOCALAPPDATA: "C:\\Users\\Alice\\AppData\\Local",
APPDATA: "C:\\Users\\Alice\\AppData\\Roaming",
};
const manifest = buildNukeManifest({
env,
homedir: "C:\\Users\\Alice",
platform: "win32",
userDataPath: "C:\\Users\\Alice\\AppData\\Roaming\\com.differentai.openwork",
});
assert.equal(manifest.preserveBootstrapPath, "C:\\Users\\Alice\\AppData\\Local\\openwork\\desktop-bootstrap.json");
assert.ok(manifest.deletePaths.includes("C:\\Users\\Alice\\AppData\\Roaming\\com.differentai.openwork"));
assert.ok(manifest.deletePaths.includes("C:\\Users\\Alice\\AppData\\Roaming\\openwork\\server.json"));
assert.ok(manifest.deletePaths.includes("C:\\Users\\Alice\\AppData\\Roaming\\openwork\\runtime.sqlite"));
assert.ok(manifest.deletePaths.includes("C:\\Users\\Alice\\AppData\\Roaming\\openwork\\tokens.json"));
assert.ok(manifest.deletePaths.includes("C:\\Users\\Alice\\AppData\\Roaming\\openwork\\env.json"));
assert.ok(manifest.deletePaths.includes("C:\\Users\\Alice\\AppData\\Roaming\\opencode"));
assert.ok(manifest.deletePaths.includes("C:\\Users\\Alice\\AppData\\Roaming\\opencode"));
assert.ok(manifest.deletePaths.includes("C:\\Users\\Alice\\.cache\\opencode"));
assert.ok(manifest.deletePaths.includes("C:\\Users\\Alice\\.config\\openwork\\desktop-bootstrap.json"));
});
test("buildNukeManifest honors OPENWORK_ELECTRON_USERDATA override", () => {
const manifest = buildNukeManifest({
env: { OPENWORK_ELECTRON_USERDATA: "/tmp/openwork-userdata" },
homedir: "/Users/alice",
platform: "darwin",
userDataPath: "/Users/alice/Library/Application Support/com.differentai.openwork",
});
assert.ok(manifest.deletePaths.includes("/tmp/openwork-userdata"));
assert.ok(!manifest.deletePaths.includes("/Users/alice/Library/Application Support/com.differentai.openwork"));
});
test("buildNukeManifest redirects HOME/XDG paths in dev mode", () => {
const manifest = buildNukeManifest({
env: { OPENWORK_DEV_MODE: "1" },
homedir: "/Users/alice",
platform: "darwin",
userDataPath: "/tmp/openwork-dev-userdata",
});
assert.equal(
manifest.preserveBootstrapPath,
"/tmp/openwork-dev-userdata/openwork-dev-data/home/.config/openwork/desktop-bootstrap.json",
);
assert.ok(manifest.deletePaths.includes("/tmp/openwork-dev-userdata"));
assert.ok(manifest.deletePaths.includes("/tmp/openwork-dev-userdata/openwork-dev-data/xdg/data/opencode"));
assert.ok(manifest.deletePaths.includes("/tmp/openwork-dev-userdata/openwork-dev-data/config/opencode"));
assert.ok(manifest.deletePaths.includes("/tmp/openwork-dev-userdata/openwork-dev-data/xdg/cache/opencode"));
assert.ok(!manifest.deletePaths.some((targetPath) => targetPath.startsWith("/Users/alice/")));
});
test("buildNukeManifest never reaches production state from a non-dev isolated profile", () => {
const userDataPath = "/Users/alice/Library/Application Support/com.differentai.openwork.dev.wt2";
const manifest = buildNukeManifest({
env: { OPENWORK_ELECTRON_APP_IDENTIFIER: "com.differentai.openwork.dev.wt2" },
homedir: "/Users/alice",
platform: "darwin",
userDataPath,
});
assert.ok(manifest.deletePaths.includes(userDataPath));
for (const productionPath of [
"/Users/alice/.config/openwork",
"/Users/alice/.config/openwork/tokens.json",
"/Users/alice/.config/openwork/runtime.sqlite",
"/Users/alice/.config/opencode",
"/Users/alice/.cache/opencode",
"/Users/alice/.local/share/opencode",
"/Users/alice/Library/Application Support/opencode",
legacyOrchestratorPath("/Users/alice"),
"/Users/alice/Library/Caches/com.differentai.openwork.ShipIt",
]) {
assert.ok(!manifest.deletePaths.includes(productionPath), `must not delete ${productionPath}`);
}
});
test("buildNukeManifest still wipes a dev profile's own orchestrator data dir", () => {
const devOrchestratorPath = `${legacyOrchestratorPath("/Users/alice")}-dev`;
const manifest = buildNukeManifest({
env: { OPENWORK_DEV_MODE: "1", OPENWORK_DATA_DIR: devOrchestratorPath },
homedir: "/Users/alice",
platform: "darwin",
userDataPath: "/tmp/openwork-dev-userdata",
});
assert.ok(manifest.deletePaths.includes(devOrchestratorPath));
assert.ok(!manifest.deletePaths.includes(legacyOrchestratorPath("/Users/alice")));
});
test("buildNukeManifest ignores an inherited XDG_CONFIG_HOME pointing at production config", () => {
const manifest = buildNukeManifest({
env: { OPENWORK_DEV_MODE: "1", XDG_CONFIG_HOME: "/Users/alice/.config" },
homedir: "/Users/alice",
platform: "darwin",
userDataPath: "/tmp/openwork-dev-userdata",
});
assert.ok(!manifest.deletePaths.some((targetPath) => targetPath.startsWith("/Users/alice/")));
});
test("buildNukeManifest excludes paths that would remove ~/.opencode/bin", () => {
const manifest = buildNukeManifest({
env: { OPENCODE_CONFIG_DIR: "/Users/alice/.opencode" },
homedir: "/Users/alice",
platform: "darwin",
userDataPath: "/tmp/openwork-userdata",
});
assert.ok(!manifest.deletePaths.includes("/Users/alice/.opencode"));
assert.ok(!manifest.deletePaths.includes("/Users/alice/.opencode/bin"));
});
test("sanitizeDesktopBootstrapConfig strips secrets and keeps deployment fields", () => {
const writtenAt = "2026-07-20T00:00:00.000Z";
const sanitized = sanitizeDesktopBootstrapConfig({
baseUrl: " https://den.example.com ",
apiBaseUrl: " https://api.den.example.com ",
requireSignin: true,
brandAppName: " Acme OpenWork ",
brandLogoUrl: " https://cdn.example.com/logo.png ",
brandIconUrl: " https://cdn.example.com/icon.png ",
handoff: { grant: "secret", denBaseUrl: "https://den.example.com" },
claimLinks: [{ id: "claim", role: "admin", token: "secret", url: "https://den.example.com", expiresAt: writtenAt }],
prepared: { skillPath: "/tmp/skill" },
}, writtenAt);
assert.deepEqual(sanitized, {
baseUrl: "https://den.example.com",
apiBaseUrl: "https://api.den.example.com",
requireSignin: true,
brandAppName: "Acme OpenWork",
brandLogoUrl: "https://cdn.example.com/logo.png",
brandIconUrl: "https://cdn.example.com/icon.png",
writtenAt,
});
});
test("sanitizeDesktopBootstrapFiles strips a BOM-wrapped valid canonical bootstrap", async () => {
await withTempDir(async (root) => {
const canonicalPath = path.join(root, "desktop-bootstrap.json");
await writeFile(canonicalPath, `\ufeff${JSON.stringify({
baseUrl: "https://den.example.com",
requireSignin: true,
handoff: { grant: "secret-grant" },
claimLinks: [{ token: "secret-token" }],
prepared: { skillPath: "/tmp/skill" },
})}`, "utf8");
assert.equal(await sanitizeDesktopBootstrapFiles({ canonicalPath, legacyPath: null }), true);
const raw = await readFile(canonicalPath, "utf8");
const parsed = JSON.parse(raw);
assert.notEqual(raw.charCodeAt(0), 0xfeff);
assert.equal(parsed.baseUrl, "https://den.example.com");
assert.equal(parsed.requireSignin, true);
assert.equal(parsed.handoff, undefined);
assert.equal(parsed.claimLinks, undefined);
assert.equal(parsed.prepared, undefined);
});
});
test("sanitizeDesktopBootstrapFiles deletes truly malformed bootstrap files", async () => {
await withTempDir(async (root) => {
const canonicalPath = path.join(root, "desktop-bootstrap.json");
await writeFile(canonicalPath, "{not-json secret-grant secret-token", "utf8");
assert.equal(await sanitizeDesktopBootstrapFiles({ canonicalPath, legacyPath: null }), false);
assert.equal(await exists(canonicalPath), false);
});
});
test("sanitizeDesktopBootstrapFiles falls back from invalid canonical to valid legacy", async () => {
await withTempDir(async (root) => {
const canonicalPath = path.join(root, "canonical", "desktop-bootstrap.json");
const legacyPath = path.join(root, "legacy", "desktop-bootstrap.json");
await mkdir(path.dirname(canonicalPath), { recursive: true });
await mkdir(path.dirname(legacyPath), { recursive: true });
await writeFile(canonicalPath, "{not-json secret-grant", "utf8");
await writeFile(legacyPath, JSON.stringify({
baseUrl: "https://legacy.example.com",
requireSignin: true,
brandAppName: "Legacy Org",
handoff: { grant: "legacy-secret-grant" },
claimLinks: [{ token: "legacy-secret-token" }],
}), "utf8");
assert.equal(await sanitizeDesktopBootstrapFiles({ canonicalPath, legacyPath }), true);
const raw = await readFile(canonicalPath, "utf8");
const parsed = JSON.parse(raw);
assert.equal(parsed.baseUrl, "https://legacy.example.com");
assert.equal(parsed.brandAppName, "Legacy Org");
assert.equal(parsed.handoff, undefined);
assert.equal(parsed.claimLinks, undefined);
assert.equal(raw.includes("legacy-secret"), false);
assert.equal(await exists(legacyPath), false);
});
});
test("runPendingNukeCleanup removes the sentinel after all pending paths are gone", async () => {
await withTempDir(async (root) => {
const targetPath = path.join(root, "locked-runtime.sqlite");
await writeFile(targetPath, "delete me", "utf8");
const pendingPath = await writePendingNuke(root, {
paths: [targetPath],
createdAt: "2026-07-20T00:00:00.000Z",
});
const result = await runPendingNukeCleanup(pendingNukeInput(root));
assert.equal(result.ran, true);
assert.deepEqual(result.pendingRetry, []);
assert.deepEqual(result.errors, []);
assert.ok(result.deleted.includes(targetPath));
assert.equal(await exists(targetPath), false);
assert.equal(await exists(pendingPath), false);
});
});
test("runPendingNukeCleanup retains the choice to remove bootstrap state", async () => {
await withTempDir(async (root) => {
const input = pendingNukeInput(root);
const bootstrapPath = path.join(input.env.XDG_CONFIG_HOME, "openwork", "desktop-bootstrap.json");
await mkdir(path.dirname(bootstrapPath), { recursive: true });
await writeFile(bootstrapPath, JSON.stringify({ baseUrl: "https://den.example.com" }), "utf8");
await writePendingNuke(root, {
paths: [bootstrapPath],
preserveBootstrap: false,
});
const result = await runPendingNukeCleanup(input);
assert.equal(result.ran, true);
assert.equal(await exists(bootstrapPath), false);
});
});
test("runPendingNukeCleanup rewrites only failed paths and removes them on the next boot", async () => {
await withTempDir(async (root) => {
const okPath = path.join(root, "ok-runtime.sqlite");
const failedPath = path.join(root, "locked-runtime.sqlite");
await writeFile(okPath, "delete me", "utf8");
await writeFile(failedPath, "locked", "utf8");
const createdAt = "2026-07-20T00:00:00.000Z";
const attemptedAt = "2026-07-21T00:00:00.000Z";
const pendingPath = await writePendingNuke(root, { paths: [okPath, failedPath], createdAt });
const firstResult = await runPendingNukeCleanup(pendingNukeInput(root), {
nowIso: attemptedAt,
removePathWithRetry: async (targetPath) => {
if (targetPath === failedPath) return new Error("simulated lock");
await rm(targetPath, { recursive: true, force: true });
return null;
},
});
const rewritten = await readJson(pendingPath);
assert.equal(firstResult.ran, true);
assert.deepEqual(firstResult.pendingRetry, [failedPath]);
assert.ok(firstResult.deleted.includes(okPath));
assert.equal(firstResult.errors.length, 1);
assert.equal(firstResult.errors[0].path, failedPath);
assert.equal(await exists(okPath), false);
assert.equal(await exists(failedPath), true);
assert.deepEqual(rewritten.paths, [failedPath]);
assert.equal(rewritten.createdAt, createdAt);
assert.equal(rewritten.attemptedAt, attemptedAt);
const secondResult = await runPendingNukeCleanup(pendingNukeInput(root));
assert.equal(secondResult.ran, true);
assert.deepEqual(secondResult.pendingRetry, []);
assert.deepEqual(secondResult.errors, []);
assert.ok(secondResult.deleted.includes(failedPath));
assert.equal(await exists(failedPath), false);
assert.equal(await exists(pendingPath), false);
});
});
test("runPendingNukeCleanup refuses replayed paths outside an isolated profile", async () => {
await withTempDir(async (root) => {
const userDataPath = path.join(root, "userData");
const productionPath = path.join(root, "home", ".config", "openwork", "tokens.json");
const profilePath = path.join(userDataPath, "openwork-server-tokens.json");
await mkdir(path.dirname(productionPath), { recursive: true });
await mkdir(userDataPath, { recursive: true });
await writeFile(productionPath, "production secrets", "utf8");
await writeFile(profilePath, "profile secrets", "utf8");
// Sentinel shaped by an older build that did not scope paths to the profile.
const pendingPath = path.join(userDataPath, ".nuke-pending.json");
await writeFile(pendingPath, `${JSON.stringify({ paths: [productionPath, profilePath] })}\n`, "utf8");
const result = await runPendingNukeCleanup({
env: { OPENWORK_ELECTRON_APP_IDENTIFIER: "com.differentai.openwork.dev.wt2" },
homedir: path.join(root, "home"),
platform: process.platform === "win32" ? "win32" : "darwin",
userDataPath,
});
assert.equal(result.ran, true);
assert.ok(result.deleted.includes(profilePath));
assert.ok(!result.deleted.includes(productionPath));
assert.equal(await exists(productionPath), true);
assert.equal(await exists(profilePath), false);
assert.equal(await exists(pendingPath), false);
});
});
test("runPendingNukeCleanup removes invalid or empty sentinels without looping", async () => {
await withTempDir(async (root) => {
const invalidPendingPath = pendingNukePath(root);
await mkdir(path.dirname(invalidPendingPath), { recursive: true });
await writeFile(invalidPendingPath, "{not-json", "utf8");
const invalidResult = await runPendingNukeCleanup(pendingNukeInput(root));
assert.equal(invalidResult.ran, false);
assert.equal(invalidResult.invalid, true);
assert.equal(await exists(invalidPendingPath), false);
const emptyPendingPath = await writePendingNuke(root, { paths: [] });
const emptyResult = await runPendingNukeCleanup(pendingNukeInput(root));
assert.equal(emptyResult.ran, false);
assert.equal(emptyResult.invalid, false);
assert.equal(await exists(emptyPendingPath), false);
});
});
test("nuke worker payload only serializes safe path inputs", () => {
const nukeInput = buildNukeWorkerNukeInput({
env: {
APPDATA: "C:\\Users\\Alice\\AppData\\Roaming",
OPENWORK_API_KEY: "secret-api-key",
OPENWORK_TOKEN: "secret-token",
OPENWORK_ELECTRON_REMOTE_DEBUG_PORT: "9888",
OPENWORK_TOKEN_STORE: "C:\\Users\\Alice\\AppData\\Roaming\\openwork\\tokens.json",
XDG_CONFIG_HOME: "/tmp/config",
},
homedir: "/tmp/home",
platform: "darwin",
userDataPath: "/tmp/userData",
preserveBootstrap: false,
});
const payload = buildNukeWorkerPayload({
parentPid: 123,
nukeInput,
appExecutablePath: process.execPath,
appArgv: ["--remote-debugging-port=9888", "--remote-debugging-address=0.0.0.0", "--secret=token"],
pendingPath: "/tmp/config/openwork/.nuke-pending.json",
nowMs: 1_000,
});
const serialized = JSON.stringify(payload);
assert.equal(payload.nukeInput.env.APPDATA, "C:\\Users\\Alice\\AppData\\Roaming");
assert.equal(payload.nukeInput.env.XDG_CONFIG_HOME, "/tmp/config");
assert.equal(payload.nukeInput.env.OPENWORK_TOKEN_STORE, "C:\\Users\\Alice\\AppData\\Roaming\\openwork\\tokens.json");
assert.equal(payload.nukeInput.preserveBootstrap, false);
assert.deepEqual(payload.appArgv, []);
assert.equal(serialized.includes("secret-api-key"), false);
assert.equal(serialized.includes("secret-token"), false);
assert.equal(serialized.includes("--secret"), false);
assert.equal(serialized.includes("0.0.0.0"), false);
assert.equal(serialized.includes("9888"), false);
assert.equal(serialized.includes("OPENWORK_ELECTRON_REMOTE_DEBUG_PORT"), false);
assert.equal(serialized.includes("OPENWORK_API_KEY"), false);
assert.equal(serialized.includes("OPENWORK_TOKEN\""), false);
const devPayload = buildNukeWorkerPayload({
parentPid: 123,
nukeInput,
appExecutablePath: process.execPath,
appArgv: ["/repo/apps/desktop/electron/main.mjs", "--remote-debugging-port=9888"],
pendingPath: "/tmp/config/openwork/.nuke-pending.json",
nowMs: 1_000,
});
assert.deepEqual(devPayload.appArgv, ["/repo/apps/desktop/electron/main.mjs"]);
});
test("scheduleNukeCleanupWorker launches Electron as Node with a detached safe payload", async () => {
await withTempDir(async (root) => {
const input = pendingNukeInput(root);
input.env.OPENWORK_ELECTRON_REMOTE_DEBUG_PORT = "9888";
const plan = {
pendingPath: pendingNukePath(root),
};
const app = fakeApp(input.userDataPath);
let spawnCommand = "";
let spawnArgs = [];
let spawnOptions = { detached: false, env: {}, shell: "unset", stdio: "" };
let unrefCalled = false;
const result = await scheduleNukeCleanupWorker({
app,
input,
plan,
execPath: process.execPath,
argv: [process.execPath, "--remote-debugging-port=9888", "--remote-debugging-address=0.0.0.0", "--ignored"],
env: { SECRET_TOKEN: "do-not-write", XDG_CONFIG_HOME: input.env.XDG_CONFIG_HOME, OPENWORK_ELECTRON_REMOTE_DEBUG_PORT: "9888" },
spawnFn: (command, args, options) => {
spawnCommand = command;
spawnArgs = args;
spawnOptions = options;
return { pid: 9876, unref: () => { unrefCalled = true; } };
},
});
const payload = await readJson(result.payloadPath);
assert.equal(result.pid, 9876);
assert.equal(unrefCalled, true);
assert.equal(spawnCommand, process.execPath);
assert.ok(spawnArgs[0].endsWith("nuke-worker.mjs"));
assert.equal(spawnArgs[1], result.payloadPath);
assert.equal(spawnOptions.detached, true);
assert.equal(spawnOptions.stdio, "ignore");
assert.equal(spawnOptions.shell, undefined);
assert.equal(spawnOptions.env.ELECTRON_RUN_AS_NODE, "1");
assert.equal(spawnOptions.env.OPENWORK_ELECTRON_REMOTE_DEBUG_PORT, undefined);
assert.equal(payload.nukeInput.env.OPENWORK_ELECTRON_REMOTE_DEBUG_PORT, undefined);
assert.deepEqual(payload.appArgv, []);
assert.equal(JSON.stringify(payload).includes("do-not-write"), false);
await rm(result.payloadPath, { force: true });
});
});
test("nuke cleanup worker waits for parent exit, clears pending path, removes payload, and launches app", async () => {
await withTempDir(async (root) => {
const targetPath = path.join(root, "userData");
await mkdir(targetPath, { recursive: true });
await writeFile(path.join(targetPath, "marker.txt"), "delete me", "utf8");
const pendingPath = await writePendingNuke(root, {
paths: [targetPath],
createdAt: "2026-07-20T00:00:00.000Z",
});
const parent = spawn(process.execPath, ["-e", "setTimeout(() => {}, 180)"], { stdio: "ignore" });
const payloadPath = path.join(root, "payload.json");
const payload = buildNukeWorkerPayload({
parentPid: parent.pid,
nukeInput: pendingNukeInput(root),
appExecutablePath: process.execPath,
appArgv: ["--remote-debugging-port=9888"],
pendingPath,
nowMs: Date.now(),
});
payload.parentWaitDeadlineAt = Date.now() + 4000;
payload.deadlineAt = Date.now() + 6000;
await writeFile(payloadPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
let launchedAfterParentExit = false;
let launchedArgs = null;
let launchEnv = {};
const result = await runNukeCleanupWorker(payloadPath, {
env: { ELECTRON_RUN_AS_NODE: "1", OPENWORK_ELECTRON_REMOTE_DEBUG_PORT: "9888" },
relaunchHandleGraceMs: 0,
spawnApp: (_command, args, options) => {
launchedAfterParentExit = parent.exitCode !== null;
launchedArgs = args;
launchEnv = options.env;
return { pid: 4321, unref: () => {} };
},
});
assert.equal(result.launchPid, 4321);
assert.equal(launchedAfterParentExit, true);
assert.deepEqual(launchedArgs, []);
assert.equal(launchEnv.ELECTRON_RUN_AS_NODE, undefined);
assert.equal(launchEnv.OPENWORK_ELECTRON_REMOTE_DEBUG_PORT, undefined);
assert.equal(await exists(targetPath), false);
assert.equal(await exists(pendingPath), false);
assert.equal(await exists(payloadPath), false);
parent.kill();
});
});
test("nuke cleanup worker still removes payload and launches app when cleanup throws", async () => {
await withTempDir(async (root) => {
const payloadPath = path.join(root, "payload.json");
const payload = buildNukeWorkerPayload({
parentPid: 0,
nukeInput: pendingNukeInput(root),
appExecutablePath: process.execPath,
appArgv: [],
pendingPath: pendingNukePath(root),
nowMs: Date.now(),
});
await writeFile(payloadPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
let launched = false;
const result = await runNukeCleanupWorker(payloadPath, {
relaunchHandleGraceMs: 0,
runPendingCleanup: async () => {
throw new Error("cleanup failed");
},
spawnApp: () => {
launched = true;
return { pid: 5432, unref: () => {} };
},
});
assert.equal(result.launchPid, 5432);
assert.equal(launched, true);
assert.equal(result.cleanup.errors[0].message, "cleanup failed");
assert.equal(await exists(payloadPath), false);
});
});
test("executeNukeFreshStart skips host-wide container cleanup on an isolated profile", async () => {
await withTempDir(async (root) => {
const runtimeManager = fakeRuntimeManager();
let cleanedContainers = 0;
runtimeManager.sandboxCleanupOpenworkContainers = async () => {
cleanedContainers += 1;
return { candidates: [], removed: [], errors: [] };
};
const input = { ...pendingNukeInput(root), env: { OPENWORK_DEV_MODE: "1" } };
await mkdir(input.userDataPath, { recursive: true });
await executeNukeFreshStart({
app: fakeApp(input.userDataPath),
session: fakeSession(),
runtimeManager,
uiControlServer: fakeUiControlServer(),
removeWindowsBrandShortcut: async () => {},
}, { input });
assert.equal(cleanedContainers, 0);
await tinyDelay();
});
});
test("executeNukeFreshStart still cleans containers on the default profile", async () => {
await withTempDir(async (root) => {
const runtimeManager = fakeRuntimeManager();
let cleanedContainers = 0;
runtimeManager.sandboxCleanupOpenworkContainers = async () => {
cleanedContainers += 1;
return { candidates: [], removed: [], errors: [] };
};
const input = pendingNukeInput(root);
await mkdir(input.userDataPath, { recursive: true });
await executeNukeFreshStart({
app: fakeApp(input.userDataPath),
session: fakeSession(),
runtimeManager,
uiControlServer: fakeUiControlServer(),
removeWindowsBrandShortcut: async () => {},
}, { input });
assert.equal(cleanedContainers, 1);
await tinyDelay();
});
});
test("executeNukeFreshStart falls back to direct relaunch when worker spawn fails", async () => {
await withTempDir(async (root) => {
const input = pendingNukeInput(root);
await mkdir(input.userDataPath, { recursive: true });
const app = fakeApp(input.userDataPath);
const receipt = await executeNukeFreshStart({
app,
session: fakeSession(),
runtimeManager: fakeRuntimeManager(),
uiControlServer: fakeUiControlServer(),
removeWindowsBrandShortcut: async () => {},
}, {
input,
removePathWithRetry: async (targetPath) => {
if (targetPath === input.userDataPath) return new Error("simulated Chromium lock");
await rm(targetPath, { recursive: true, force: true });
return null;
},
scheduleCleanupWorker: async () => {
throw new Error("spawn failed");
},
});
assert.equal(receipt.relaunchMode, "direct");
assert.equal(receipt.workerScheduled, false);
assert.ok(receipt.pendingRetry.includes(input.userDataPath));
assert.ok(receipt.errors.some((error) => error.path === "nuke-cleanup-worker" && error.message === "spawn failed"));
assert.equal(await exists(pendingNukePath(root)), true);
await tinyDelay();
assert.equal(app.relaunchCount, 1);
assert.equal(app.quitCount, 1);
});
});
test("executeNukeFreshStart schedules cleanup worker for pending paths", async () => {
await withTempDir(async (root) => {
const input = pendingNukeInput(root);
await mkdir(input.userDataPath, { recursive: true });
const app = fakeApp(input.userDataPath);
let scheduled = false;
const receipt = await executeNukeFreshStart({
app,
session: fakeSession(),
runtimeManager: fakeRuntimeManager(),
uiControlServer: fakeUiControlServer(),
removeWindowsBrandShortcut: async () => {},
}, {
input,
removePathWithRetry: async (targetPath) => {
if (targetPath === input.userDataPath) return new Error("simulated Chromium lock");
await rm(targetPath, { recursive: true, force: true });
return null;
},
scheduleCleanupWorker: async () => {
scheduled = true;
return { pid: 1234, payloadPath: path.join(root, "payload.json") };
},
});
assert.equal(receipt.relaunchMode, "cleanup_worker");
assert.equal(receipt.workerScheduled, true);
assert.equal(scheduled, true);
assert.ok(receipt.pendingRetry.includes(input.userDataPath));
assert.equal(await exists(pendingNukePath(root)), true);
await tinyDelay();
assert.equal(app.relaunchCount, 0);
assert.equal(app.quitCount, 1);
});
});
test("executeNukeFreshStart relaunches directly when no paths remain pending", async () => {
await withTempDir(async (root) => {
const input = pendingNukeInput(root);
await mkdir(input.userDataPath, { recursive: true });
const app = fakeApp(input.userDataPath);
const receipt = await executeNukeFreshStart({
app,
session: fakeSession(),
runtimeManager: fakeRuntimeManager(),
uiControlServer: fakeUiControlServer(),
removeWindowsBrandShortcut: async () => {},
}, { input });
assert.equal(receipt.relaunchMode, "direct");
assert.equal(receipt.workerScheduled, false);
assert.deepEqual(receipt.pendingRetry, []);
await tinyDelay();
assert.equal(app.relaunchCount, 1);
assert.equal(app.quitCount, 1);
});
});
test("executeNukeFreshStart removes the bootstrap when preservation is disabled", async () => {
await withTempDir(async (root) => {
const input = { ...pendingNukeInput(root), preserveBootstrap: false };
const bootstrapPath = path.join(input.env.XDG_CONFIG_HOME, "openwork", "desktop-bootstrap.json");
await mkdir(path.dirname(bootstrapPath), { recursive: true });
await writeFile(bootstrapPath, JSON.stringify({ baseUrl: "https://den.example.com" }), "utf8");
await mkdir(input.userDataPath, { recursive: true });
const app = fakeApp(input.userDataPath);
const receipt = await executeNukeFreshStart({
app,
session: fakeSession(),
runtimeManager: fakeRuntimeManager(),
uiControlServer: fakeUiControlServer(),
removeWindowsBrandShortcut: async () => {},
}, { input });
assert.equal(receipt.preservedBootstrap, false);
assert.equal(await exists(bootstrapPath), false);
await tinyDelay();
});
});