Files
stack/agents/darkwing/work/s6-review/probe/close-kill.mjs
T
jason.woltjeandClaude Opus 5.5 f19787ee60 docs(review): row 41 slice 1 S6 round 1 review, changes (darkwing)
R1 and R3 block: a late broker reply shifts later replies on the host IPC
channel, and a dangling workspace symlink passes the gate. R2 (close waits
on an open launch.sock client) should be fixed in the same round. Probes,
mutants and suite outputs alongside. Comment 26995 on #1523, queue rev 247.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 20:21:28 -05:00

44 lines
2.9 KiB
JavaScript

// Does launcher.close() SIGKILL a session whose runner never answers its
// SIGTERM? Launch the PM, SIGSTOP its runner from outside the namespace (a
// stopped process handles no signal but SIGKILL), close the host, and time
// it. Imports from the review worktree by absolute path.
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";
const WT = process.env.WT;
const { bootConfig, loadSystem } = await import(`${WT}/packages/cli/src/config.mjs`);
const { startHost, startTimeOf } = await import(`${WT}/packages/cli/src/host.mjs`);
const { createLauncher } = await import(`${WT}/packages/cli/src/launcher.mjs`);
const { readSessions, launchLogFile } = await import(`${WT}/packages/seat/src/session.mjs`);
const { fixture } = await import(`${WT}/packages/cli/tests/helpers.mjs`);
const root = mkdtempSync(join(tmpdir(), "r41-kill-"));
const f = fixture(root, "acme", (doc) => {
doc.roles.coder.vars.model = "claude-sonnet-5-5";
doc.launch = { by: "pm", instances: ["coder"], max: { opus: 1, sonnet: 1 } };
return doc;
});
mkdirSync(f.dataRoot, { mode: 0o700 });
const adapter = join(root, "adapter.sh");
writeFileSync(adapter, `exec '${process.execPath}' '${WT}/packages/harness/tests/fixtures/fake-adapter.mjs'\n`);
const system = loadSystem({ env: f.env });
const logs = [];
const host = await startHost({ boot: bootConfig({ system, businessId: "acme", env: f.env }), business: "acme", log: (l) => logs.push(`host: ${l}`) });
const launcher = createLauncher({ host, system, env: f.env, adapters: { pi: adapter, "claude-code": adapter }, sessionDefaults: { pollInterval: 100 }, versions: { pi: "0.85.1", claude: null }, log: (l) => logs.push(`launcher: ${l}`) });
await launcher.listen();
const pm = await launcher.launchPm();
const [s] = readSessions(f.dataRoot);
const runnerLog = join(f.dataRoot, "launches", "acme", pm.run, "runner.log");
for (let i = 0; i < 300 && !readFileSync(runnerLog, "utf8").includes("claimed by run"); i++) await new Promise((r) => setTimeout(r, 50));
console.log(`pm run ${pm.run}: session pid ${s.pid}, runner pid ${s.runnerPid}, claimed`);
process.kill(s.runnerPid, "SIGSTOP");
console.log("runner SIGSTOPped");
const t0 = Date.now();
const code = await host.close(0);
console.log(`host.close -> ${code} after ${Date.now() - t0} ms`);
console.log(`session pid alive: ${startTimeOf(s.pid) === s.startTime}; runner pid alive: ${startTimeOf(s.runnerPid) === s.runnerStartTime}`);
const ends = readFileSync(launchLogFile(f.dataRoot, "acme"), "utf8").trim().split("\n").map((l) => JSON.parse(l)).filter((e) => e.event === "end");
console.log("launch-log end lines:", JSON.stringify(ends.map((e) => ({ run: e.run, reason: e.reason, exitCode: e.exitCode, signal: e.signal }))));
console.log("sessions.json after:", JSON.stringify(readSessions(f.dataRoot)));
console.log(logs.filter((l) => /ended|run /.test(l)).join("\n"));
rmSync(root, { recursive: true, force: true });