@@ -595,15 +595,15 @@ const DSN_RESOLVE_TIMEOUT_MS = 15_000;
595595/**
596596 * Resolve DSNs with a concurrency limit and overall timeout.
597597 *
598- * Uses p-limit's `map` helper for concurrency control and
599- * `AbortSignal.timeout` for a self-cleaning deadline. Queued tasks
600- * check the abort signal before doing work (same pattern as
601- * code-scanner's earlyExit flag from PR #414).
598+ * Uses p-limit's `map` helper for concurrency control and races it
599+ * against `AbortSignal.timeout` so the CLI never hangs indefinitely.
600+ * Queued tasks check the abort signal before doing work (same pattern
601+ * as code-scanner's earlyExit flag from PR #414). In-flight tasks that
602+ * already started are abandoned on timeout — their individual HTTP
603+ * timeouts (30s in sentry-client.ts) bound them independently.
602604 *
603- * Note: Cannot use `limit.clearQueue()` — as documented in
604- * code-scanner.ts, it causes cleared promises to never settle,
605- * hanging the map forever. Instead, queued tasks return null when
606- * the signal is aborted.
605+ * Results are written to a shared array so that tasks completing before
606+ * the deadline are captured even when the overall operation times out.
607607 *
608608 * @param dsns - Deduplicated DSNs to resolve
609609 * @returns Array of resolved targets (null for failures/timeouts)
@@ -614,14 +614,31 @@ async function resolveDsnsWithTimeout(
614614 const limit = pLimit ( DSN_RESOLVE_CONCURRENCY ) ;
615615 const signal = AbortSignal . timeout ( DSN_RESOLVE_TIMEOUT_MS ) ;
616616
617- const results = await limit . map ( dsns , ( dsn ) => {
617+ // Shared results array — tasks write their result as they complete,
618+ // so partial results survive timeout.
619+ const results : ( ResolvedTarget | null ) [ ] = new Array ( dsns . length ) . fill ( null ) ;
620+
621+ const mapDone = limit . map ( dsns , ( dsn , i ) => {
618622 if ( signal . aborted ) {
619623 return Promise . resolve ( null ) ;
620624 }
621- return resolveDsnToTarget ( dsn ) ;
625+ return resolveDsnToTarget ( dsn ) . then ( ( target ) => {
626+ results [ i ] = target ;
627+ return target ;
628+ } ) ;
629+ } ) ;
630+
631+ // Race limit.map against the abort signal so in-flight tasks
632+ // don't block the timeout.
633+ const aborted = new Promise < "timeout" > ( ( resolve ) => {
634+ signal . addEventListener ( "abort" , ( ) => resolve ( "timeout" ) , { once : true } ) ;
622635 } ) ;
636+ const raceResult = await Promise . race ( [
637+ mapDone . then ( ( ) => "done" as const ) ,
638+ aborted ,
639+ ] ) ;
623640
624- if ( signal . aborted ) {
641+ if ( raceResult === "timeout" ) {
625642 log . warn (
626643 `DSN resolution timed out after ${ DSN_RESOLVE_TIMEOUT_MS / 1000 } s, returning partial results`
627644 ) ;
0 commit comments