
























@@ -155,8 +155,25 @@ function readParentPidFromPs(pid: number, spawnTimeoutMs: number): number | null
155155 * `/proc/<pid>/status` payloads) — there is no reachable override for
156156 * runtime callers to mutate.
157157 */
158-export function getSelfAndAncestorPidsSync(spawnTimeoutMs = SPAWN_TIMEOUT_MS): Set<number> {
158+export function getSelfAndAncestorPidsSync(
159+spawnTimeoutMs = SPAWN_TIMEOUT_MS,
160+additionalProtectedPid?: number,
161+): Set<number> {
159162const pids = new Set<number>([process.pid]);
163+// Additional protected PID. The caller passes an inherited gateway service
164+// PID captured from `OPENCLAW_GATEWAY_SERVICE_PID` BEFORE the current
165+// process overwrites that env with its own PID. This closes the suicide-
166+// kill loophole when ancestor walk cannot see the parent gateway because
167+// the caller was reparented to the supervisor (PID 1 / launchd) between
168+// spawn and cleanup — e.g. `~/.zshrc` running `(openclaw gateway &)`
169+// inside a shell snapshot whose parent zsh has already exited.
170+if (
171+additionalProtectedPid != null &&
172+Number.isFinite(additionalProtectedPid) &&
173+additionalProtectedPid > 0
174+) {
175+pids.add(additionalProtectedPid);
176+}
160177const immediateParent = getParentPid();
161178if (!Number.isFinite(immediateParent) || immediateParent <= 0) {
162179return pids;
@@ -257,12 +274,16 @@ function verifyGatewayPidByArgvSync(pid: number, spawnTimeoutMs: number): boolea
257274return args != null && isGatewayArgv(args, { allowGatewayBinary: true });
258275}
259276260-function parsePidsFromLsofOutput(stdout: string, spawnTimeoutMs: number): number[] {
277+function parsePidsFromLsofOutput(
278+stdout: string,
279+spawnTimeoutMs: number,
280+additionalProtectedPid?: number,
281+): number[] {
261282// Deduplicate: dual-stack listeners (IPv4 + IPv6) cause lsof to emit the
262283// same PID twice. Return each PID at most once to avoid double-killing.
263284// Exclude self and ancestors — terminating any ancestor cascade-kills the
264285// caller via the supervisor, recreating the #68451 restart loop.
265-const excluded = getSelfAndAncestorPidsSync(spawnTimeoutMs);
286+const excluded = getSelfAndAncestorPidsSync(spawnTimeoutMs, additionalProtectedPid);
266287const pids: number[] = [];
267288for (const entry of parseLsofEntries(stdout)) {
268289if (excluded.has(entry.pid)) {
@@ -285,8 +306,11 @@ function parsePidsFromLsofOutput(stdout: string, spawnTimeoutMs: number): number
285306 * and its ancestors (same invariant as the lsof path — see
286307 * `getSelfAndAncestorPidsSync`).
287308 */
288-function filterVerifiedWindowsGatewayPids(rawPids: number[]): number[] {
289-const excluded = getSelfAndAncestorPidsSync();
309+function filterVerifiedWindowsGatewayPids(
310+rawPids: number[],
311+additionalProtectedPid?: number,
312+): number[] {
313+const excluded = getSelfAndAncestorPidsSync(SPAWN_TIMEOUT_MS, additionalProtectedPid);
290314return uniqueValues(rawPids)
291315.filter((pid) => Number.isFinite(pid) && pid > 0 && !excluded.has(pid))
292316.filter((pid) => {
@@ -298,8 +322,9 @@ function filterVerifiedWindowsGatewayPids(rawPids: number[]): number[] {
298322function filterVerifiedWindowsGatewayPidsResult(
299323rawPids: number[],
300324processArgsResult: (pid: number) => WindowsProcessArgsResult,
325+additionalProtectedPid?: number,
301326): WindowsListeningPidsResult {
302-const excluded = getSelfAndAncestorPidsSync();
327+const excluded = getSelfAndAncestorPidsSync(SPAWN_TIMEOUT_MS, additionalProtectedPid);
303328const verified: number[] = [];
304329for (const pid of uniqueValues(rawPids)) {
305330if (!Number.isFinite(pid) || pid <= 0 || excluded.has(pid)) {
@@ -316,32 +341,51 @@ function filterVerifiedWindowsGatewayPidsResult(
316341return { ok: true, pids: verified };
317342}
318343319-function findVerifiedWindowsGatewayPidsOnPortSync(port: number): number[] {
320-return filterVerifiedWindowsGatewayPids(readWindowsListeningPidsOnPortSync(port));
344+function findVerifiedWindowsGatewayPidsOnPortSync(
345+port: number,
346+additionalProtectedPid?: number,
347+): number[] {
348+return filterVerifiedWindowsGatewayPids(
349+readWindowsListeningPidsOnPortSync(port),
350+additionalProtectedPid,
351+);
321352}
322353323-function findVerifiedWindowsGatewayPidsOnPortResultSync(port: number): WindowsListeningPidsResult {
354+function findVerifiedWindowsGatewayPidsOnPortResultSync(
355+port: number,
356+additionalProtectedPid?: number,
357+): WindowsListeningPidsResult {
324358const result = readWindowsListeningPidsResultSync(port);
325359if (!result.ok) {
326360return result;
327361}
328-return filterVerifiedWindowsGatewayPidsResult(result.pids, (pid) =>
329-readWindowsProcessArgsResultSync(pid),
362+return filterVerifiedWindowsGatewayPidsResult(
363+result.pids,
364+(pid) => readWindowsProcessArgsResultSync(pid),
365+additionalProtectedPid,
330366);
331367}
332368333369/**
334370 * Find PIDs of gateway processes listening on the given port using synchronous lsof.
335371 * Returns only PIDs that belong to openclaw gateway processes (not the current process).
372+ *
373+ * `additionalProtectedPid`: see `getSelfAndAncestorPidsSync`. When the caller
374+ * has captured an inherited gateway service PID from
375+ * `OPENCLAW_GATEWAY_SERVICE_PID` (before overwriting it with its own PID),
376+ * pass it through here so the cleanup cannot kill a launchd-managed
377+ * grandparent that became a sibling after the caller was reparented to the
378+ * supervisor.
336379 */
337380export function findGatewayPidsOnPortSync(
338381port: number,
339382spawnTimeoutMs = SPAWN_TIMEOUT_MS,
383+additionalProtectedPid?: number,
340384): number[] {
341385if (process.platform === "win32") {
342386// Use the shared Windows port inspection (PowerShell / netstat) with
343387// command-line verification to find only openclaw gateway processes.
344-return findVerifiedWindowsGatewayPidsOnPortSync(port);
388+return findVerifiedWindowsGatewayPidsOnPortSync(port, additionalProtectedPid);
345389}
346390const lsof = resolveLsofCommandSync();
347391const res = spawnSync(lsof, ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-Fpc"], {
@@ -368,7 +412,7 @@ export function findGatewayPidsOnPortSync(
368412);
369413return [];
370414}
371-return parsePidsFromLsofOutput(res.stdout, spawnTimeoutMs);
415+return parsePidsFromLsofOutput(res.stdout, spawnTimeoutMs, additionalProtectedPid);
372416}
373417374418/**
@@ -585,8 +629,18 @@ function waitForPortFreeSync(port: number): void {
585629 * the port and enter an EADDRINUSE restart loop.
586630 *
587631 * Called before service restart commands to prevent port conflicts.
632+ *
633+ * `additionalProtectedPid`: the caller may pass an inherited gateway service
634+ * PID (captured from `OPENCLAW_GATEWAY_SERVICE_PID` BEFORE the caller
635+ * overwrites that env with its own PID) to keep the running launchd-managed
636+ * gateway out of the kill set even when the ancestor walk has lost the link
637+ * via reparenting. See `getSelfAndAncestorPidsSync` for the suicide-kill
638+ * scenario this closes.
588639 */
589-export function cleanStaleGatewayProcessesSync(portOverride?: number): number[] {
640+export function cleanStaleGatewayProcessesSync(
641+portOverride?: number,
642+additionalProtectedPid?: number,
643+): number[] {
590644try {
591645const port =
592646typeof portOverride === "number" && Number.isFinite(portOverride) && portOverride > 0
@@ -595,14 +649,17 @@ export function cleanStaleGatewayProcessesSync(portOverride?: number): number[]
595649const stalePids =
596650process.platform === "win32"
597651 ? (() => {
598-const result = findVerifiedWindowsGatewayPidsOnPortResultSync(port);
652+const result = findVerifiedWindowsGatewayPidsOnPortResultSync(
653+port,
654+additionalProtectedPid,
655+);
599656if (result.ok) {
600657return result.pids;
601658}
602659waitForPortFreeSync(port);
603660return [];
604661})()
605- : findGatewayPidsOnPortSync(port);
662+ : findGatewayPidsOnPortSync(port, SPAWN_TIMEOUT_MS, additionalProtectedPid);
606663if (stalePids.length === 0) {
607664return [];
608665}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。