Skip to content

fix: silent hang after pagination bar — add concurrency limit and progress feedback for line-number resolution #99

Description

@shouze

Bug report

Observed behaviour

After the pagination progress bar completes (Fetching results from GitHub… ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ page 10/10), the CLI freezes silently for up to ~2 minutes before the TUI appears.
There is no spinner, no message — the terminal looks stuck.

The hang is worse on slow/mobile networks (tethering) and sometimes resolves by re-running on a better connection.

Root cause

fetchAllResults in src/api.ts performs a second wave of HTTP requests after pagination, with no visual feedback:

search/code API  →  [progress bar visible]  →  done
raw.githubusercontent.com × N files  →  [total silence]  ←  THE HANG
TUI appears

These requests fetch each unique file's raw content from raw.githubusercontent.com to compute absolute line numbers (fragment-relative indices only are returned by the GitHub Search API). Three compounding issues:

  1. Promise.all with no concurrency cap: with 200+ unique files, 200+ HTTP requests fire simultaneously. On a saturated network this stalls the event loop.
  2. No progress feedback: the user sees nothing after the bar finishes.
  3. fetchWithRetry retries silently up to 3× per request, multiplying delays on flaky networks.

Steps to reproduce

  • Run a query that returns many results (close to the 1 000-result cap).
  • Use a slow or mobile network.
  • Observe the ~2-minute freeze after the pagination bar.

Proposed fix

1. Add concurrentMap to src/api-utils.ts

A generic semaphore-based helper that runs async tasks with a bounded concurrency (no new dependencies):

export async function concurrentMap<T, R>(
  items: T[],
  fn: (item: T, index: number) => Promise<R>,
  { concurrency = 20 }: { concurrency?: number } = {},
): Promise<R[]>

Add unit tests in src/api-utils.test.ts.

2. Add buildLineResolutionProgress to src/api.ts

A pure function (same pattern as buildFetchProgress) building the progress line for the resolution phase. Export for unit testing.

  Resolving line numbers… ▓▓▓▓▓░░░░░░░░░░░░░░░  12/47

3. Refactor the resolution phase in fetchAllResults

  • Replace Promise.all(urlsToFetch.map(...)) with concurrentMap(..., { concurrency: 20 }).
  • Add a per-request AbortController with a 5 s timeout; on timeout fall back silently to fragment-relative line numbers (existing behaviour).
  • Write/update the stderr progress line on each completed request.
  • Clear the line cleanly before returning (same as the pagination bar).

4. Add unit tests

  • buildLineResolutionProgress — pure function, testable without network.
  • concurrentMap — verify concurrency cap and error handling.

Verification

bun test               # all tests green (new tests included)
bun run lint           # zero errors
bun run format:check   # no diff
bun run knip           # no unused exports
bun run build.ts       # binary compiles

Manual: run a query returning many results on a slow network — the new progress bar should appear immediately after pagination, and the TUI should open within ≤ 10 s on tethering.


Parameters chosen

Parameter Value Rationale
Concurrency cap 20 Balance speed / server courtesy
Per-request timeout 5 s Raw files are small; silent fallback on timeout

Branch: fix/line-resolution-hang

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingux

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions