Skip to content

Commit f4754ec

Browse files
committed
test(daemon): authenticate recovery workers
1 parent b6ca021 commit f4754ec

1 file changed

Lines changed: 24 additions & 4 deletions

File tree

packages/coding-agent/test/suite/regressions/4603-worker-recovery.test.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "node:fs";
1313
import { createConnection, type Socket } from "node:net";
1414
import { join, resolve } from "node:path";
15+
import { Writable } from "node:stream";
1516
import { afterEach, describe, expect, it } from "vitest";
1617
import { APP_NAME, ENV_AGENT_DIR } from "../../../src/config.js";
1718
import { getProcessStartId } from "../../../src/core/session-lease.js";
@@ -26,6 +27,8 @@ import {
2627
import {
2728
DAEMON_WORKER_ACTIVE_SESSION_ID_ENV,
2829
DAEMON_WORKER_ROLE_ENV,
30+
DAEMON_WORKER_STARTUP_GATE_COMMIT,
31+
DAEMON_WORKER_STARTUP_GATE_FD_ENV,
2932
DAEMON_WORKER_SUPERVISOR_SOCKET_ENV,
3033
DAEMON_WORKER_TOKEN_ENV,
3134
type DaemonWorkerDescriptor,
@@ -94,6 +97,7 @@ const fixturePath = resolve(__dirname, "../../fixtures/eng-4600-supervisor-fixtu
9497
const fauxExtensionPath = resolve(__dirname, "../../fixtures/eng-4600-faux-extension.ts");
9598
const cliPath = resolve(__dirname, "../../../src/cli.ts");
9699
const tsxPath = resolve(__dirname, "../../../../../node_modules/tsx/dist/cli.mjs");
100+
const tsxLoaderPath = resolve(__dirname, "../../../../../node_modules/tsx/dist/loader.mjs");
97101
const tsconfigPath = resolve(__dirname, "../../../../../tsconfig.json");
98102
const supervisorRegistryDirEnv = "PRIME_AGENT_INTERNAL_DAEMON_SUPERVISOR_REGISTRY_DIR";
99103
const handles = new Set<ProcessHandle>();
@@ -174,10 +178,11 @@ function spawnStandaloneWorker(
174178
token: string,
175179
extraEnv: NodeJS.ProcessEnv = {},
176180
): ProcessHandle {
177-
return trackProcess(
181+
// The tsx CLI wrapper drops fd 3 when it relaunches Node, so load tsx in-process.
182+
const worker = trackProcess(
178183
spawn(
179184
paths.executablePath,
180-
[tsxPath, cliPath, "--mode", "daemon", "--daemon-socket", workerSocketPath, "--offline"],
185+
["--import", tsxLoaderPath, cliPath, "--mode", "daemon", "--daemon-socket", workerSocketPath, "--offline"],
181186
{
182187
cwd: paths.agentDir,
183188
env: {
@@ -188,15 +193,22 @@ function spawnStandaloneWorker(
188193
[DAEMON_WORKER_ROLE_ENV]: "1",
189194
[DAEMON_WORKER_TOKEN_ENV]: token,
190195
[DAEMON_WORKER_ACTIVE_SESSION_ID_ENV]: "eng-4603-worker",
196+
[DAEMON_WORKER_STARTUP_GATE_FD_ENV]: "3",
191197
[DAEMON_WORKER_SUPERVISOR_SOCKET_ENV]: paths.socketPath,
192198
PI_OFFLINE: "1",
193199
TSX_TSCONFIG_PATH: tsconfigPath,
194200
},
195-
stdio: ["ignore", "pipe", "pipe"],
201+
stdio: ["ignore", "pipe", "pipe", "pipe"],
196202
},
197203
),
198204
"worker",
199205
);
206+
const startupGate = worker.child.stdio[3];
207+
if (!(startupGate instanceof Writable)) {
208+
throw new Error("Standalone worker startup gate is not writable");
209+
}
210+
startupGate.end(DAEMON_WORKER_STARTUP_GATE_COMMIT);
211+
return worker;
200212
}
201213

202214
function trackProcess(child: ChildProcess, role: FixtureProcessIdentity["role"]): ProcessHandle {
@@ -342,6 +354,9 @@ function isFixtureDescendant(pid: number, rootPid: number, processes: Map<number
342354
}
343355

344356
function signalFixtureProcess(identity: FixtureProcessIdentity, signal: NodeJS.Signals): boolean {
357+
if (identity.pid === process.pid) {
358+
throw new Error(`Refusing to signal current Vitest process ${process.pid} during fixture cleanup`);
359+
}
345360
const state = fixtureProcessState(identity);
346361
if (state === "exited") return false;
347362
if (state === "unverified") {
@@ -990,7 +1005,12 @@ describe("ENG-4603 worker recovery convergence", () => {
9901005
processStartId?: string;
9911006
};
9921007
expect(record.pid).toBe(process.pid);
993-
expect(record.processStartId).toBe(getProcessStartId(process.pid));
1008+
const processStartId = record.processStartId;
1009+
expect(processStartId).toBe(getProcessStartId(process.pid));
1010+
if (!processStartId) throw new Error("Shutdown admission did not capture the Vitest process identity");
1011+
expect(() => signalFixtureProcess({ pid: record.pid, processStartId, role: "supervisor" }, "SIGSTOP")).toThrow(
1012+
`Refusing to signal current Vitest process ${process.pid} during fixture cleanup`,
1013+
);
9941014
const renewal = Reflect.get(first, "renewal") as object | undefined;
9951015
const refreshTimer = renewal
9961016
? (Reflect.get(renewal, "refreshTimer") as ReturnType<typeof setInterval> | undefined)

0 commit comments

Comments
 (0)