惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
PCI Perspectives
PCI Perspectives
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Heimdal Security Blog
S
Security @ Cisco Blogs
N
News | PayPal Newsroom
J
Java Code Geeks
罗磊的独立博客
Security Archives - TechRepublic
Security Archives - TechRepublic
N
News and Events Feed by Topic
V
V2EX
WordPress大学
WordPress大学
Google Online Security Blog
Google Online Security Blog
N
News and Events Feed by Topic
www.infosecurity-magazine.com
www.infosecurity-magazine.com
月光博客
月光博客
AI
AI
小众软件
小众软件
The GitHub Blog
The GitHub Blog
MongoDB | Blog
MongoDB | Blog
A
Arctic Wolf
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
美团技术团队
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tailwind CSS Blog
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
F
Full Disclosure
B
Blog RSS Feed
Forbes - Security
Forbes - Security
S
SegmentFault 最新的问题
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
Jina AI
Jina AI
Cisco Talos Blog
Cisco Talos Blog
U
Unit 42
Project Zero
Project Zero
H
Hacker News: Front Page
Y
Y Combinator Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The Cloudflare Blog
大猫的无限游戏
大猫的无限游戏
S
Secure Thoughts
The Hacker News
The Hacker News
Microsoft Azure Blog
Microsoft Azure Blog

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4 test: trim doctor command hotspots · openclaw/openclaw@c66f16a test: isolate agent auth and spawn hotspots · openclaw/openclaw@9285935 test: stabilize MCP startup disposal race · openclaw/openclaw@dd9d2eb test: merge browser contract server suites · openclaw/openclaw@5817a76 test: narrow ollama provider discovery setup · openclaw/openclaw@a0d9598 build: declare qa-lab aimock runtime dependency · openclaw/openclaw@24431e5 test: speed up safe-bins exec harness · openclaw/openclaw@ee856ab test: preserve tool helpers in embedded runner mocks · openclaw/openclaw@acd86a0 refactor: move memory embeddings into provider plugins · openclaw/openclaw@77e6e4c test: reuse system-run temp fixtures · openclaw/openclaw@7e9ff0f test: trim hotspot wait overhead · openclaw/openclaw@12a59b0 Check: avoid duplicate boundary prep · openclaw/openclaw@baf11b8 test: reduce hotspot fixture overhead · openclaw/openclaw@3a59edd feat(ui): overhaul settings and slash command UX (#67819) thanks @Bun… · openclaw/openclaw@2cfb660 QA Matrix: exit cleanly on failure · openclaw/openclaw@42805d2 QA Matrix: isolate scenario coverage · openclaw/openclaw@7e659e1 Matrix: refresh crypto bootstrap state · openclaw/openclaw@94081d8 QA Lab: add provider registry · openclaw/openclaw@bb7e982 Matrix: add plugin changelog · openclaw/openclaw@4acab55 test: trim more hotspot overhead · openclaw/openclaw@f485311 test: trim remaining hotspot tests · openclaw/openclaw@6ba8626 test: narrow hotspot mocks · openclaw/openclaw@dbc8179 test: isolate gemini embedding request helpers · openclaw/openclaw@cd330f5 test: trim memory and mcp hotspots · openclaw/openclaw@fd48dfa test: slim provider registry mocks · openclaw/openclaw@2e08c77 test: harden Parallels update smoke · openclaw/openclaw@1a98090 feat: default Anthropic to Opus 4.7 · openclaw/openclaw@628b454 fix: harden node-host shell payload mutability checks · openclaw/openclaw@75c551e fix: land node-host approval binding for native binaries (#66731) (th… · openclaw/openclaw@29919bb CI: add daily schedule to CodeQL workflow (#67645) · openclaw/openclaw@69d25f5 fix(gateway): capture config hash after plugin auto-enable to prevent… · openclaw/openclaw@8c11210 fix: repair sanitized replay tool results before send (#67620) (thank… · openclaw/openclaw@c3c7a99 fix: restrict HTML timeout short-circuit to transient statuses · openclaw/openclaw@de129a6 fix: keep TUI watchdog bound to active run (#67401) (thanks @xantorres) · openclaw/openclaw@3525273 Gateway/skills: dedupe skills prefix-match + drop dead fallback on log · openclaw/openclaw@d7f489f Extensions/lmstudio: back off inference preload after consecutive fai… · openclaw/openclaw@b555214 TUI/streaming: add watchdog that resets the activity indicator after … · openclaw/openclaw@f44ab20 Agents/tool-loop: enable unknown-tool stream guard by default · openclaw/openclaw@36ed367 Gateway/skills: invalidate session skills snapshot on config write · openclaw/openclaw@b23d59a fix: classify HTML provider error pages correctly (#67642) (thanks @s… · openclaw/openclaw@e588e90 fix(skills): remove unused model-usage import (#67641) · openclaw/openclaw@55f05df docs(changelog): credit codex fix superseded PRs · openclaw/openclaw@e485f24 fix(openai-codex): normalize stale transport metadata in resolution a… · openclaw/openclaw@90801ba CI: pin Docker-related GitHub Actions (#67632) · openclaw/openclaw@f697b01 Android: modernize WebView and discovery API usage (#67627) · openclaw/openclaw@44a6e50 fix(deps): bump hono to 4.12.14 and @hono/node-server to 1.19.14 (GHS… · openclaw/openclaw@fbccc18 fix(deps): bump dompurify to 3.4.0 (#67614) · openclaw/openclaw@2c2dc00 CI: add explicit permissions to all workflow jobs (fixes code-scannin… · openclaw/openclaw@01b7516 fix: register bundled TTS providers and route overrides correctly (#6… · openclaw/openclaw@6ea3cdd fix: align host tilde paths with OS home (#62804) (thanks @stainlu) · openclaw/openclaw@ecfaf64 fix: flush creds queue before reconnect socket open (#67464) (thanks … · openclaw/openclaw@405c63f fix: strip standalone <function> tool call tags from visible text (#6… · openclaw/openclaw@78df859 fix(agents): preserve cli session metadata before transcript persist … · openclaw/openclaw@898fd04 docs(changelog): move cli transcript entry · openclaw/openclaw@c1817c6 fix(agents): normalize cli transcript api field · openclaw/openclaw@3a3fae0 docs(changelog): note cli transcript persistence · openclaw/openclaw@6c343f1 fix(agents): persist cli transcript turns · openclaw/openclaw@b8ef507 fix(msteams): harden security-sensitive flows (#65841) · openclaw/openclaw@c56b56e [Dashboard] Fix exec approval modal overflow for long command content… · openclaw/openclaw@053c5b0 Docs: remove QA changelog entry · openclaw/openclaw@7fd5771 QA: fix private runtime source loading (#67428) · openclaw/openclaw@d5933af docs(gateway): correct protocol.md schema path, hello-ok example, aut… · openclaw/openclaw@489404d CI: pin Node 22 runners to 22.18.0 · openclaw/openclaw@4ffa621 models.authStatus: normalize provider ids + tighten env-backed escape… · openclaw/openclaw@f2fdb9d Update CHANGELOG.md · openclaw/openclaw@7694a92 test(parallels): clean up npm update guard jobs · openclaw/openclaw@045ea7b Plugins: prefer scanDir override paths · openclaw/openclaw@b2974da fix(dreaming): default storage.mode to "separate" so phase blocks sto… · openclaw/openclaw@8c392f0 fix(memory-core): skip dreaming transcript ingestion via session stor… · openclaw/openclaw@a1b01f0 fix: dedupe replayed exec.finished node events (#67281) · openclaw/openclaw@5dcf526
fix(e2e): harden Parallels host timeouts · openclaw/openclaw@28550c3
vincentkoc · 2026-06-01 · via Recent Commits to openclaw:main
1-

import { spawn, spawnSync, type SpawnOptions } from "node:child_process";

1+

import { spawn, spawnSync, type SpawnOptions, type SpawnSyncReturns } from "node:child_process";

22

import { writeFile } from "node:fs/promises";

33

import path from "node:path";

44

import { fileURLToPath } from "node:url";

@@ -9,6 +9,13 @@ import type { CommandResult, RunOptions } from "./types.ts";

991010

export const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");

111112+

const HOST_COMMAND_MAX_BUFFER_BYTES = 50 * 1024 * 1024;

13+

const HOST_COMMAND_WRAPPER_EXTRA_BUFFER_BYTES = 1024 * 1024;

14+

const HOST_COMMAND_WRAPPER_BACKSTOP_MS = 5_000;

15+

const HOST_COMMAND_CHILD_PID_PREFIX = "__OPENCLAW_HOST_COMMAND_CHILD_PID__";

16+

const HOST_COMMAND_SPAWN_ERROR_PREFIX = "__OPENCLAW_HOST_COMMAND_SPAWN_ERROR__";

17+

const HOST_COMMAND_TIMEOUT_PREFIX = "__OPENCLAW_HOST_COMMAND_TIMEOUT__";

18+1219

type HostCommandInvocation = {

1320

args: string[];

1421

command: string;

@@ -47,6 +54,161 @@ export function die(message: string): never {

4754

process.exit(1);

4855

}

495657+

function signalHostCommandProcess(pid: number | undefined, signal: NodeJS.Signals): void {

58+

if (!pid) {

59+

return;

60+

}

61+

try {

62+

if (process.platform === "win32") {

63+

process.kill(pid, signal);

64+

} else {

65+

process.kill(-pid, signal);

66+

}

67+

} catch (error) {

68+

const code = (error as NodeJS.ErrnoException).code;

69+

if (code !== "ESRCH") {

70+

warn(

71+

`failed to send ${signal} to timed host command process ${pid}: ${

72+

code ?? String(error)

73+

}`,

74+

);

75+

}

76+

}

77+

}

78+79+

const POSIX_TIMEOUT_WRAPPER = String.raw`

80+

const { spawn } = require("node:child_process");

81+

const { readFileSync, writeSync } = require("node:fs");

82+83+

const payload = JSON.parse(readFileSync(0, "utf8"));

84+

const child = spawn(payload.command, payload.args, {

85+

cwd: payload.cwd,

86+

detached: true,

87+

env: payload.env,

88+

shell: payload.shell,

89+

stdio: ["pipe", "pipe", "pipe"],

90+

});

91+

writeSync(

92+

3,

93+

${JSON.stringify(HOST_COMMAND_CHILD_PID_PREFIX)} + JSON.stringify({

94+

pid: child.pid || null,

95+

}) + "\n",

96+

);

97+98+

let timedOut = false;

99+

let killTimer;

100+

let outputExceeded = false;

101+

let stderrBytes = 0;

102+

let stdoutBytes = 0;

103+104+

function writeAllSync(fd, chunk) {

105+

let offset = 0;

106+

while (offset < chunk.byteLength) {

107+

offset += writeSync(fd, chunk, offset, chunk.byteLength - offset);

108+

}

109+

}

110+111+

function signalGroup(signal) {

112+

if (!child.pid) {

113+

return;

114+

}

115+

try {

116+

process.kill(-child.pid, signal);

117+

} catch (error) {

118+

if (error && error.code !== "ESRCH") {

119+

process.stderr.write("failed to send " + signal + " to timed host command process " + child.pid + ": " + (error.code || String(error)) + "\n");

120+

}

121+

}

122+

}

123+124+

function forwardBounded(stream, chunk) {

125+

const currentBytes = stream === "stdout" ? stdoutBytes : stderrBytes;

126+

const nextBytes = currentBytes + chunk.byteLength;

127+

const limit = payload.maxBufferBytes;

128+

if (stream === "stdout") {

129+

stdoutBytes = nextBytes;

130+

} else {

131+

stderrBytes = nextBytes;

132+

}

133+

if (outputExceeded) {

134+

return;

135+

}

136+

if (nextBytes <= limit) {

137+

writeAllSync(stream === "stdout" ? 1 : 2, chunk);

138+

return;

139+

}

140+

outputExceeded = true;

141+

const allowedBytes = Math.max(0, limit - currentBytes);

142+

if (allowedBytes > 0) {

143+

writeAllSync(stream === "stdout" ? 1 : 2, chunk.subarray(0, allowedBytes));

144+

}

145+

writeAllSync(

146+

2,

147+

Buffer.from("host command output exceeded " + limit + " bytes; terminating process group\n"),

148+

);

149+

signalGroup("SIGKILL");

150+

}

151+152+

for (const signal of ["SIGHUP", "SIGINT", "SIGTERM"]) {

153+

process.once(signal, () => {

154+

signalGroup(signal);

155+

process.kill(process.pid, signal);

156+

});

157+

}

158+159+

const timeout = setTimeout(() => {

160+

timedOut = true;

161+

signalGroup("SIGTERM");

162+

killTimer = setTimeout(() => signalGroup("SIGKILL"), 100);

163+

killTimer.unref();

164+

}, payload.timeoutMs);

165+

timeout.unref();

166+167+

child.stdout.on("data", (chunk) => forwardBounded("stdout", chunk));

168+

child.stderr.on("data", (chunk) => forwardBounded("stderr", chunk));

169+

child.stdin.on("error", (error) => {

170+

if (error && error.code !== "EPIPE" && error.code !== "ECONNRESET") {

171+

writeAllSync(2, Buffer.from("host command stdin write failed: " + (error.code || String(error)) + "\n"));

172+

}

173+

});

174+

child.on("error", (error) => {

175+

clearTimeout(timeout);

176+

if (killTimer) {

177+

clearTimeout(killTimer);

178+

}

179+

writeSync(

180+

3,

181+

${JSON.stringify(HOST_COMMAND_SPAWN_ERROR_PREFIX)} + JSON.stringify({

182+

code: error.code || null,

183+

message: error.message,

184+

}) + "\n",

185+

);

186+

process.stderr.write(error.message + "\n");

187+

process.exit(127);

188+

});

189+

child.on("close", (code, signal) => {

190+

clearTimeout(timeout);

191+

if (killTimer) {

192+

clearTimeout(killTimer);

193+

}

194+

if (timedOut) {

195+

signalGroup("SIGKILL");

196+

writeSync(3, ${JSON.stringify(HOST_COMMAND_TIMEOUT_PREFIX)} + "{}\n");

197+

process.exit(124);

198+

}

199+

if (outputExceeded) {

200+

process.exit(1);

201+

}

202+

process.exit(code ?? (signal ? 128 : 1));

203+

});

204+205+

if (payload.input != null) {

206+

child.stdin.end(payload.input);

207+

} else {

208+

child.stdin.end();

209+

}

210+

`;

211+50212

export function shellQuote(value: string): string {

51213

return `'${value.replaceAll("'", `'"'"'`)}'`;

52214

}

@@ -116,20 +278,46 @@ export function resolveHostCommandInvocation(

116278

export function run(command: string, args: string[], options: RunOptions = {}): CommandResult {

117279

const env = { ...process.env, ...options.env };

118280

const invocation = resolveHostCommandInvocation(command, args, { env });

119-

const result = spawnSync(invocation.command, invocation.args, {

120-

cwd: options.cwd ?? repoRoot,

121-

encoding: "utf8",

122-

env: invocation.env ?? env,

123-

input: options.input,

124-

killSignal: "SIGKILL",

125-

maxBuffer: 50 * 1024 * 1024,

126-

stdio: options.quiet ? ["pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe"],

127-

shell: invocation.shell,

128-

timeout: options.timeoutMs,

129-

windowsVerbatimArguments: invocation.windowsVerbatimArguments,

130-

});

281+

const usesPosixTimedWrapper = process.platform !== "win32" && options.timeoutMs !== undefined;

282+

const result =

283+

usesPosixTimedWrapper

284+

? runPosixTimedCommandSync(invocation, env, options)

285+

: spawnSync(invocation.command, invocation.args, {

286+

cwd: options.cwd ?? repoRoot,

287+

encoding: "utf8",

288+

env: invocation.env ?? env,

289+

input: options.input,

290+

killSignal: "SIGKILL",

291+

maxBuffer: HOST_COMMAND_MAX_BUFFER_BYTES,

292+

stdio: options.quiet ? ["pipe", "pipe", "pipe"] : ["pipe", "pipe", "pipe"],

293+

shell: invocation.shell,

294+

timeout: options.timeoutMs,

295+

windowsVerbatimArguments: invocation.windowsVerbatimArguments,

296+

});

131297132-

const timedOut = (result.error as NodeJS.ErrnoException | undefined)?.code === "ETIMEDOUT";

298+

let wrapperTimedOut = false;

299+

if (usesPosixTimedWrapper) {

300+

const wrapperControl = typeof result.output[3] === "string" ? result.output[3] : "";

301+

const outerWrapperTimedOut =

302+

(result.error as NodeJS.ErrnoException | undefined)?.code === "ETIMEDOUT";

303+

if (outerWrapperTimedOut) {

304+

signalHostCommandProcess(parsePosixTimedWrapperChildPid(wrapperControl), "SIGKILL");

305+

}

306+

wrapperTimedOut = outerWrapperTimedOut || hasPosixTimedWrapperTimeout(wrapperControl);

307+

const spawnError = parsePosixTimedWrapperSpawnError(wrapperControl);

308+

if (spawnError) {

309+

throw spawnError;

310+

}

311+

}

312+

const timedOut =

313+

wrapperTimedOut || (result.error as NodeJS.ErrnoException | undefined)?.code === "ETIMEDOUT";

314+

if (wrapperTimedOut && options.check !== false) {

315+

const error = new Error(

316+

`${command} ${args.join(" ")} timed out after ${options.timeoutMs}ms`,

317+

) as NodeJS.ErrnoException;

318+

error.code = "ETIMEDOUT";

319+

throw error;

320+

}

133321

if (result.error && !(timedOut && options.check === false)) {

134322

throw result.error;

135323

}

@@ -152,6 +340,76 @@ export function run(command: string, args: string[], options: RunOptions = {}):

152340

return commandResult;

153341

}

154342343+

function hasPosixTimedWrapperTimeout(controlOutput: string): boolean {

344+

return controlOutput.split("\n").some((entry) => entry.startsWith(HOST_COMMAND_TIMEOUT_PREFIX));

345+

}

346+347+

function parsePosixTimedWrapperChildPid(controlOutput: string): number | undefined {

348+

const line = controlOutput

349+

.split("\n")

350+

.find((entry) => entry.startsWith(HOST_COMMAND_CHILD_PID_PREFIX));

351+

if (!line) {

352+

return undefined;

353+

}

354+

try {

355+

const parsed = JSON.parse(line.slice(HOST_COMMAND_CHILD_PID_PREFIX.length)) as {

356+

pid?: unknown;

357+

};

358+

return typeof parsed.pid === "number" ? parsed.pid : undefined;

359+

} catch {

360+

return undefined;

361+

}

362+

}

363+364+

function parsePosixTimedWrapperSpawnError(stderr: string): NodeJS.ErrnoException | null {

365+

const line = stderr

366+

.split("\n")

367+

.find((entry) => entry.startsWith(HOST_COMMAND_SPAWN_ERROR_PREFIX));

368+

if (!line) {

369+

return null;

370+

}

371+

const raw = line.slice(HOST_COMMAND_SPAWN_ERROR_PREFIX.length);

372+

try {

373+

const parsed = JSON.parse(raw) as { code?: unknown; message?: unknown };

374+

const error = new Error(

375+

typeof parsed.message === "string" ? parsed.message : "host command spawn failed",

376+

) as NodeJS.ErrnoException;

377+

if (typeof parsed.code === "string") {

378+

error.code = parsed.code;

379+

}

380+

return error;

381+

} catch {

382+

return new Error("host command spawn failed") as NodeJS.ErrnoException;

383+

}

384+

}

385+386+

function runPosixTimedCommandSync(

387+

invocation: HostCommandInvocation,

388+

env: NodeJS.ProcessEnv,

389+

options: RunOptions,

390+

): SpawnSyncReturns<string> {

391+

const payload = JSON.stringify({

392+

args: invocation.args,

393+

command: invocation.command,

394+

cwd: options.cwd ?? repoRoot,

395+

env: invocation.env ?? env,

396+

input: options.input,

397+

maxBufferBytes: HOST_COMMAND_MAX_BUFFER_BYTES,

398+

shell: invocation.shell,

399+

timeoutMs: options.timeoutMs,

400+

});

401+

return spawnSync(process.execPath, ["-e", POSIX_TIMEOUT_WRAPPER], {

402+

cwd: options.cwd ?? repoRoot,

403+

encoding: "utf8",

404+

env,

405+

input: payload,

406+

killSignal: "SIGKILL",

407+

maxBuffer: HOST_COMMAND_MAX_BUFFER_BYTES * 2 + HOST_COMMAND_WRAPPER_EXTRA_BUFFER_BYTES,

408+

stdio: ["pipe", "pipe", "pipe", "pipe"],

409+

timeout: (options.timeoutMs ?? 0) + HOST_COMMAND_WRAPPER_BACKSTOP_MS,

410+

});

411+

}

412+155413

export function sh(script: string, options: RunOptions = {}): CommandResult {

156414

return run("bash", ["-lc", script], options);

157415

}

@@ -166,11 +424,31 @@ export async function runStreaming(

166424

const invocation = resolveHostCommandInvocation(command, args, { env });

167425

const child = spawn(invocation.command, invocation.args, {

168426

cwd: options.cwd ?? repoRoot,

427+

detached: process.platform !== "win32" && options.timeoutMs != null,

169428

env: invocation.env ?? env,

170429

shell: invocation.shell,

171430

stdio: ["pipe", "pipe", "pipe"],

172431

windowsVerbatimArguments: invocation.windowsVerbatimArguments,

173432

} satisfies SpawnOptions);

433+

const childPid = child.pid;

434+

const parentSignalHandlers = new Map<NodeJS.Signals, () => void>();

435+

const removeParentSignalHandlers = (): void => {

436+

for (const [signal, handler] of parentSignalHandlers) {

437+

process.off(signal, handler);

438+

}

439+

parentSignalHandlers.clear();

440+

};

441+

if (process.platform !== "win32" && options.timeoutMs != null) {

442+

for (const signal of ["SIGHUP", "SIGINT", "SIGTERM"] as const) {

443+

const handler = (): void => {

444+

signalHostCommandProcess(childPid, signal);

445+

removeParentSignalHandlers();

446+

process.kill(process.pid, signal);

447+

};

448+

parentSignalHandlers.set(signal, handler);

449+

process.once(signal, handler);

450+

}

451+

}

174452175453

let log = "";

176454

const append = (chunk: Buffer): void => {

@@ -195,21 +473,36 @@ export async function runStreaming(

195473

}

196474197475

let timedOut = false;

476+

let killTimer: NodeJS.Timeout | undefined;

198477

const timer =

199478

options.timeoutMs == null

200479

? undefined

201480

: setTimeout(() => {

202481

timedOut = true;

203-

child.kill("SIGTERM");

204-

setTimeout(() => child.kill("SIGKILL"), 2_000).unref();

482+

signalHostCommandProcess(childPid, "SIGTERM");

483+

killTimer = setTimeout(() => signalHostCommandProcess(childPid, "SIGKILL"), 2_000);

484+

killTimer.unref();

205485

}, options.timeoutMs);

206486207-

child.on("error", reject);

487+

child.on("error", (error) => {

488+

if (killTimer) {

489+

clearTimeout(killTimer);

490+

}

491+

removeParentSignalHandlers();

492+

reject(error);

493+

});

208494

child.on("close", (code, signal) => {

209495

void (async () => {

210496

if (timer) {

211497

clearTimeout(timer);

212498

}

499+

if (killTimer) {

500+

clearTimeout(killTimer);

501+

}

502+

removeParentSignalHandlers();

503+

if (timedOut) {

504+

signalHostCommandProcess(childPid, "SIGKILL");

505+

}

213506

if (options.logPath) {

214507

await writeFile(options.logPath, log, "utf8");

215508

}