diff --git a/.agents/skills/impeccable/scripts/concept-seed.mjs b/.agents/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.agents/skills/impeccable/scripts/concept-seed.mjs +++ b/.agents/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.claude/skills/impeccable/scripts/concept-seed.mjs b/.claude/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.claude/skills/impeccable/scripts/concept-seed.mjs +++ b/.claude/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.cursor/skills/impeccable/scripts/concept-seed.mjs b/.cursor/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.cursor/skills/impeccable/scripts/concept-seed.mjs +++ b/.cursor/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.gemini/skills/impeccable/scripts/concept-seed.mjs b/.gemini/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.gemini/skills/impeccable/scripts/concept-seed.mjs +++ b/.gemini/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.github/skills/impeccable/scripts/concept-seed.mjs b/.github/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.github/skills/impeccable/scripts/concept-seed.mjs +++ b/.github/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.grok/skills/impeccable/scripts/concept-seed.mjs b/.grok/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.grok/skills/impeccable/scripts/concept-seed.mjs +++ b/.grok/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.kiro/skills/impeccable/scripts/concept-seed.mjs b/.kiro/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.kiro/skills/impeccable/scripts/concept-seed.mjs +++ b/.kiro/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.opencode/skills/impeccable/scripts/concept-seed.mjs b/.opencode/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.opencode/skills/impeccable/scripts/concept-seed.mjs +++ b/.opencode/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.pi/skills/impeccable/scripts/concept-seed.mjs b/.pi/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.pi/skills/impeccable/scripts/concept-seed.mjs +++ b/.pi/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.qoder/skills/impeccable/scripts/concept-seed.mjs b/.qoder/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.qoder/skills/impeccable/scripts/concept-seed.mjs +++ b/.qoder/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.rovodev/skills/impeccable/scripts/concept-seed.mjs b/.rovodev/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.rovodev/skills/impeccable/scripts/concept-seed.mjs +++ b/.rovodev/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.trae-cn/skills/impeccable/scripts/concept-seed.mjs b/.trae-cn/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.trae-cn/skills/impeccable/scripts/concept-seed.mjs +++ b/.trae-cn/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.trae/skills/impeccable/scripts/concept-seed.mjs b/.trae/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.trae/skills/impeccable/scripts/concept-seed.mjs +++ b/.trae/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/.vibe/skills/impeccable/scripts/concept-seed.mjs b/.vibe/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/.vibe/skills/impeccable/scripts/concept-seed.mjs +++ b/.vibe/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); } diff --git a/plugin/skills/impeccable/scripts/concept-seed.mjs b/plugin/skills/impeccable/scripts/concept-seed.mjs index 2280c100f..93ad63a66 100644 --- a/plugin/skills/impeccable/scripts/concept-seed.mjs +++ b/plugin/skills/impeccable/scripts/concept-seed.mjs @@ -79,6 +79,13 @@ const here = dirname(fileURLToPath(import.meta.url)); const CATALOG_DIR = process.env.IMPECCABLE_CATALOG_DIR || here; const API_BASE = (process.env.IMPECCABLE_API_URL || 'https://impeccable.style/api').replace(/\/$/, ''); const API_TIMEOUT_MS = Number(process.env.IMPECCABLE_API_TIMEOUT || 4000); +// All API calls in one seed run share a single deadline so an unreachable +// network degrades after one timeout total, never one timeout per call. +let apiDeadline = null; +function apiBudgetMs() { + if (apiDeadline === null) apiDeadline = Date.now() + API_TIMEOUT_MS; + return Math.max(0, apiDeadline - Date.now()); +} const localStates = new Map(); function loadLocal(catalogDir = CATALOG_DIR) { @@ -120,9 +127,15 @@ async function fetchRoll({ scope, key, mode, reroll }) { const params = new URLSearchParams({ scope, key, reroll: String(reroll) }); if (mode) params.set('mode', mode); const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { - const response = await fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }); + // Race the budget explicitly: abort signals do not reliably cancel the + // TCP connect phase, so a blackholed route would otherwise stall ~10s. + const response = await Promise.race([ + fetch(`${API_BASE}/roll?${params}`, { signal: controller.signal }), + new Promise(resolveTimeout => setTimeout(() => resolveTimeout(null), apiBudgetMs())), + ]); + if (!response) return null; if (!response.ok) return null; const roll = await response.json(); if (!Array.isArray(roll.challengers) || roll.challengers.length === 0) return null; @@ -143,7 +156,7 @@ function telemetryDisabled() { export async function pingChosen({ chosenId, key, scope, mode }) { if (telemetryDisabled() || !chosenId) return false; const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS); + const timer = setTimeout(() => controller.abort(), apiBudgetMs()); try { await fetch(`${API_BASE}/chosen`, { method: 'POST', @@ -543,4 +556,7 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); process.exitCode = 1; } + // A raced-out fetch may still hold a socket; exit explicitly so the CLI + // never lingers on a dead network path after output is written. + process.exit(process.exitCode ?? 0); }