diff --git a/.agents/skills/impeccable/scripts/context.mjs b/.agents/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.agents/skills/impeccable/scripts/context.mjs +++ b/.agents/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.claude/skills/impeccable/scripts/context.mjs b/.claude/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.claude/skills/impeccable/scripts/context.mjs +++ b/.claude/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.cursor/skills/impeccable/scripts/context.mjs b/.cursor/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.cursor/skills/impeccable/scripts/context.mjs +++ b/.cursor/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.gemini/skills/impeccable/scripts/context.mjs b/.gemini/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.gemini/skills/impeccable/scripts/context.mjs +++ b/.gemini/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.github/skills/impeccable/scripts/context.mjs b/.github/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.github/skills/impeccable/scripts/context.mjs +++ b/.github/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.grok/skills/impeccable/scripts/context.mjs b/.grok/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.grok/skills/impeccable/scripts/context.mjs +++ b/.grok/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.hermes/skills/impeccable/scripts/context.mjs b/.hermes/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.hermes/skills/impeccable/scripts/context.mjs +++ b/.hermes/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.kiro/skills/impeccable/scripts/context.mjs b/.kiro/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.kiro/skills/impeccable/scripts/context.mjs +++ b/.kiro/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.opencode/skills/impeccable/scripts/context.mjs b/.opencode/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.opencode/skills/impeccable/scripts/context.mjs +++ b/.opencode/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.pi/skills/impeccable/scripts/context.mjs b/.pi/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.pi/skills/impeccable/scripts/context.mjs +++ b/.pi/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.qoder/skills/impeccable/scripts/context.mjs b/.qoder/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.qoder/skills/impeccable/scripts/context.mjs +++ b/.qoder/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.rovodev/skills/impeccable/scripts/context.mjs b/.rovodev/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.rovodev/skills/impeccable/scripts/context.mjs +++ b/.rovodev/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.trae-cn/skills/impeccable/scripts/context.mjs b/.trae-cn/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.trae-cn/skills/impeccable/scripts/context.mjs +++ b/.trae-cn/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.trae/skills/impeccable/scripts/context.mjs b/.trae/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.trae/skills/impeccable/scripts/context.mjs +++ b/.trae/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/.vibe/skills/impeccable/scripts/context.mjs b/.vibe/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/.vibe/skills/impeccable/scripts/context.mjs +++ b/.vibe/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) { diff --git a/plugin/skills/impeccable/scripts/context.mjs b/plugin/skills/impeccable/scripts/context.mjs index 203bb378e..ea5cad4c0 100644 --- a/plugin/skills/impeccable/scripts/context.mjs +++ b/plugin/skills/impeccable/scripts/context.mjs @@ -1013,6 +1013,27 @@ async function fetchLatestSkillVersion() { } } +// Destroy fetch's global undici dispatcher before process.exit(): a live +// keep-alive socket trips a libuv assertion on Windows/Node 24 after a +// successful boot (nodejs/node#56645, issue #573). +async function destroyFetchDispatcher() { + const dispatcher = globalThis[Symbol.for('undici.globalDispatcher.1')]; + if (dispatcher && typeof dispatcher.destroy === 'function') { + try { await dispatcher.destroy(); } catch { /* exit regardless */ } + } +} + +// Drain the boot payload before process.exit(): a live pipe that has not +// flushed yet is truncated when Node tears down (issue #573 review). Then +// close fetch so Windows teardown does not abort on the keep-alive socket. +async function finishCli(output) { + await new Promise((resolve) => { + process.stdout.write(output, () => resolve()); + }); + await destroyFetchDispatcher(); + process.exit(0); +} + // Two instructions used to sit in one directive: ask, and "if they agree, run // it". Nothing gated the second on an answer, and the same sentence said to // continue without waiting, so a run that could never establish agreement was @@ -1159,8 +1180,7 @@ async function cli() { appendImageToolsDirective(parts); appendStalenessDirective(parts, ctx, cliOptions); if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); - process.exit(0); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } const parts = [`# PRODUCT.md\n\n${ctx.product.trim()}`]; if (ctx.hasDesign) { @@ -1206,7 +1226,7 @@ async function cli() { } } if (updateDirective) parts.push(updateDirective); - process.stdout.write(parts.join('\n\n---\n\n') + '\n'); + await finishCli(parts.join('\n\n---\n\n') + '\n'); } function parseCliOptions(args) {