Skip to content

Fix systematic exit delay after search output (interactive + non-interactive) - #192

Merged
shouze merged 2 commits into
mainfrom
fix/tui-exit-delay
Aug 23, 2026
Merged

Fix systematic exit delay after search output (interactive + non-interactive)#192
shouze merged 2 commits into
mainfrom
fix/tui-exit-delay

Conversation

@shouze

@shouze shouze commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes a systematic delay between the moment the final output is printed and the moment the process actually returns control to the shell — noticeable in both interactive and non-interactive (--no-interactive / CI) mode.

Root cause 1 — interactive mode (src/tui.ts)

Keyboard input is read via for await (const chunk of process.stdin). Breaking out of that loop (on Enter, q, or Ctrl+C) forces the JS engine to await the async iterator's implicit return(), which destroys the underlying Readable and waits for its 'close' event before the break actually completes. The output is already printed by then (console.log runs before the break), so the user sees the result immediately but the shell prompt only comes back once that stream teardown resolves.

Fix: call process.stdin.unref() right after setRawMode(false) in both exit paths. An unref'd handle can no longer keep the event loop alive, so the process can exit as soon as the teardown starts instead of waiting for it to fully settle.

Root cause 2 — non-interactive mode (github-code-search.ts)

The post-output "check for update" step races checkForUpdate() against a 2 s timeout via Promise.race:

await Promise.race([
  checkForUpdate(...),
  new Promise((res) => setTimeout(() => { ...; res(null); }, 2000)),
]);

The losing branch's setTimeout is never cleared. Node/Bun timers are ref'd by default, so even when checkForUpdate() wins the race in a few hundred ms, the orphaned 2 s timer keeps the process alive until it actually fires — a near-constant ~2 s delay on every CI/--no-interactive run.

Fix: .unref() the timer so it can't keep the process alive on its own, and clearTimeout it once the race settles (avoids a stray abort() firing after the flow has already moved on). The 2 s network cap behaviour is unchanged.

How did you verify your code works?

  • bun run lint — zero errors
  • bun run format:check — no diff
  • bun test — 871 tests pass, 0 failures, no regressions
  • Manual repro/verification (not unit-testable — tui.ts and the CLI entry point are side-effectful, per AGENTS.md):
    • Interactive: run a search, press Enter to confirm selection — shell prompt now returns immediately instead of after a noticeable pause.
    • Non-interactive: run with --no-interactive (or CI=true) — process now exits right after printing output instead of ~2 s later.

Copilot AI lite review requested due to automatic review settings August 23, 2026 23:10
@github-actions

Copy link
Copy Markdown

Coverage after merging fix/tui-exit-delay into main will be

96.22%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src
   aggregate.ts100%100%100%100%
   api-utils.ts93.20%100%93.75%93.13%101–103, 65, 73, 86–87, 91–92
   api.ts94.74%100%100%94.07%340–344, 405, 422, 63–69
   cache.ts94.67%100%100%94.29%139–141, 39
   completions.ts99.42%100%100%99.37%270
   group.ts99.50%100%97.44%100%
   output.ts99.29%100%95.65%99.61%80
   regex.ts99.39%100%100%99.34%329
   render.ts89.82%100%88.24%89.89%172, 196–201, 203–205, 207–208, 229, 417–418, 442–444, 510–514, 526–527, 532–539, 541–549, 551–552
   scroll-cooldown.ts100%100%100%100%
   upgrade.ts88.38%100%94.44%87.89%128, 131, 133, 153, 167–168, 188–195, 198–204, 209, 214, 250–253
src/render
   filter-match.ts97.44%100%92.31%100%
   filter.ts100%100%100%100%
   highlight.ts96.63%100%90.40%99.31%284–285
   layout-constants.ts100%100%100%100%
   mouse-hit.ts100%100%100%100%
   mouse.ts100%100%100%100%
   rows.ts97.58%100%100%97.44%168, 54–55
   selection.ts100%100%100%100%
   summary.ts100%100%100%100%
   team-pick.ts100%100%100%100%
   terminal.ts100%100%100%100%

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR removes a consistent “exit lag” after printing results by ensuring that ref’d async handles (stdin stream teardown and a timeout timer) can’t keep the event loop alive after the work is complete. It aligns with the project’s CLI/TUI architecture by keeping the fixes localized to the side-effectful entry points (tui.ts and github-code-search.ts) without altering the pure rendering/output logic.

Changes:

  • Interactive mode: process.stdin.unref() is called during cleanup so breaking out of the for await (const chunk of process.stdin) loop doesn’t hold the process open while stdin teardown completes.
  • Non-interactive/CI mode: the 2s update-check timeout is unref’d and explicitly cleared after Promise.race settles to prevent the losing timer from delaying process exit.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/tui.ts Unrefs stdin on both cleanup paths so interactive exit doesn’t wait on stdin async iterator teardown.
github-code-search.ts Unrefs and clears the update-check timeout so non-interactive runs don’t linger due to an orphaned timer.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@shouze
shouze merged commit fa85f2c into main Aug 23, 2026
6 checks passed
@shouze
shouze deleted the fix/tui-exit-delay branch August 23, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants