fix(process): disable detached spawn on Windows to fix empty exec output (#18035)

The supervisor's child adapter always spawned with `detached: true`,
which creates a new process group. On Windows Scheduled Tasks (headless,
no console), this prevents stdout/stderr pipes from properly connecting,
causing all exec tool output to silently disappear.

The old exec path (pre-supervisor refactor) never used `detached: true`.
The regression was introduced in cd44a0d01 (refactor process spawning).

Changes:
- child.ts: set `detached: false` on Windows, keep `detached: true` on
  POSIX (where it's needed to survive parent exit). Skip the no-detach
  fallback on Windows since it's already the default.
- child.test.ts: platform-aware assertions for detached behavior.

Fixes #18035
Fixes #17806
This commit is contained in:
artale
2026-02-16 14:00:45 +01:00
committed by Peter Steinberger
parent d0a5ee0176
commit a1a1f56841
2 changed files with 24 additions and 9 deletions

View File

@@ -60,8 +60,15 @@ describe("createChildAdapter", () => {
options?: { detached?: boolean };
fallbacks?: Array<{ options?: { detached?: boolean } }>;
};
expect(spawnArgs.options?.detached).toBe(true);
expect(spawnArgs.fallbacks?.[0]?.options?.detached).toBe(false);
// On Windows, detached defaults to false (headless Scheduled Task compat);
// on POSIX, detached is true with a no-detach fallback.
if (process.platform === "win32") {
expect(spawnArgs.options?.detached).toBe(false);
expect(spawnArgs.fallbacks).toEqual([]);
} else {
expect(spawnArgs.options?.detached).toBe(true);
expect(spawnArgs.fallbacks?.[0]?.options?.detached).toBe(false);
}
adapter.kill();

View File

@@ -42,11 +42,17 @@ export async function createChildAdapter(params: {
const stdinMode = params.stdinMode ?? (params.input !== undefined ? "pipe-closed" : "inherit");
// On Windows, `detached: true` creates a new process group and can prevent
// stdout/stderr pipes from connecting when running under a Scheduled Task
// (headless, no console). Default to `detached: false` on Windows; on
// POSIX systems keep `detached: true` so the child survives parent exit.
const useDetached = process.platform !== "win32";
const options: SpawnOptions = {
cwd: params.cwd,
env: params.env ? toStringEnv(params.env) : undefined,
stdio: ["pipe", "pipe", "pipe"],
detached: true,
detached: useDetached,
windowsHide: true,
windowsVerbatimArguments: params.windowsVerbatimArguments,
};
@@ -59,12 +65,14 @@ export async function createChildAdapter(params: {
const spawned = await spawnWithFallback({
argv: resolvedArgv,
options,
fallbacks: [
{
label: "no-detach",
options: { detached: false },
},
],
fallbacks: useDetached
? [
{
label: "no-detach",
options: { detached: false },
},
]
: [],
});
const child = spawned.child as ChildProcessWithoutNullStreams;