diff --git a/github-code-search.ts b/github-code-search.ts index 902aac8..3625dc0 100644 --- a/github-code-search.ts +++ b/github-code-search.ts @@ -465,15 +465,22 @@ async function searchAction( // Race against a 2 s timeout so slow networks never delay the exit. // Fix: use AbortController so the in-flight fetch is actually cancelled on timeout. const updateAbortController = new AbortController(); + // Fix: the losing Promise.race branch's timer is never cleared, and by default + // keeps the process alive until it fires — causing a ~2 s exit delay on every + // non-interactive run even when checkForUpdate resolves instantly. unref() lets + // the process exit as soon as the real work is done; clearTimeout on the winning + // path avoids a stray abort() firing after we've already moved on. + let updateCheckTimer!: ReturnType; const latestTag = await Promise.race([ checkForUpdate(VERSION, GITHUB_TOKEN, updateAbortController.signal), - new Promise((res) => - setTimeout(() => { + new Promise((res) => { + updateCheckTimer = setTimeout(() => { updateAbortController.abort(); res(null); - }, 2000), - ), + }, 2000).unref(); + }), ]).catch(() => null); + clearTimeout(updateCheckTimer); if (latestTag) { const w = 55; // Fix: compute all widths from totalWidth so corners always align. diff --git a/src/tui.ts b/src/tui.ts index 850fc3a..a629768 100644 --- a/src/tui.ts +++ b/src/tui.ts @@ -308,6 +308,9 @@ export async function runInteractive( if (statsDebounceTimer !== null) clearTimeout(statsDebounceTimer); process.stdin.setRawMode(false); process.off("SIGWINCH", onResize); + // Unref stdin so the pending `for await` read can't keep the event loop + // alive while breaking out of the loop awaits the stream's teardown. + process.stdin.unref(); // Set flag to break out of the event loop and allow stdout buffer to flush // before process termination. process.exit() may terminate too early. shouldExit = true; @@ -659,6 +662,9 @@ export async function runInteractive( process.stdout.write(ANSI_CLEAR); process.stdin.setRawMode(false); process.off("SIGWINCH", onResize); + // Unref stdin so the pending `for await` read can't keep the event loop + // alive while breaking out of the loop awaits the stream's teardown. + process.stdin.unref(); if (statsDebounceTimer !== null) clearTimeout(statsDebounceTimer); console.log( buildOutput(groups, query, org, excludedRepos, excludedExtractRefs, format, outputType, {