Skip to content

fix(vendor): surface mid-extraction death as exit 1 in vendor-codelldb.js - #392

Merged
debugmcpdev merged 4 commits into
mainfrom
fix/389-vendor-exit-guard
Aug 20, 2026
Merged

fix(vendor): surface mid-extraction death as exit 1 in vendor-codelldb.js#392
debugmcpdev merged 4 commits into
mainfrom
fix/389-vendor-exit-guard

Conversation

@debugmcpdev

Copy link
Copy Markdown
Collaborator

Fixes #389.

Root cause

extract-zip@2.0.1's promise settles only on yauzl 'close'/'error', and its async 'entry' handler is a floating promise by construction. When the entry pump stalls, nothing remains on the event loop: Node drains and exits 0 with no failure output — bypassing the failure summary, which is the script's only failure→exit-code path. This burned a docker-publish run per occurrence until v0.24.2 moved the image build to shell vendoring; the script still runs via postinstall/prebuild and in release.yml.

Fix — three independent layers

  1. Extraction watchdog (extractVsixWithWatchdog): Promise.race of extractZip against a timeout (default 120 s, CODELLDB_EXTRACT_TIMEOUT_MS). The pending timer keeps the event loop alive for the whole extraction window — the silent-drain mode becomes impossible during extraction — and a stall becomes a rejection that flows through the existing per-attempt catch, the CODELLDB_DOWNLOAD_RETRIES loop, and the failure summary. No new control flow.
  2. Premature-exit guard: a process.on('exit') listener (registered only when the script is invoked directly, so importing the module stays side-effect free) forces exit code 1 with a requested/completed/unresolved-platforms diagnostic whenever the process would exit 0 before main() settled. This covers any future stall mode (e.g. a hung fetch body stream), not just extraction.
  3. invokedDirectly hardening: both sides realpath'd, so a symlinked invocation path can no longer silently skip main() and exit 0.

No inner extract-retry was added: with stalls converted to rejections, the existing outer retry loop covers the transient case, and the artifact cache is only written after successful extraction so a failure can't poison it. IS_CI soft/hard exit semantics unchanged.

Tests

New packages/codelldb-common/tests/vendor-codelldb-script.test.ts (network-free, via CODELLDB_TEST_STALL_EXTRACTION / CODELLDB_TEST_SIMULATE_DRAIN hooks):

  • Spawn-based exit-code contract: skip path → 0; simulated event-loop drain → 1 + "Premature exit" diagnostic (the canonical vendor-codelldb.js: intermittent extraction crash exits 0 — failure not propagated #389 repro — exits 0 without this fix); local-only failure → 1 via the summary with no guard double-report.
  • Import-based: watchdog rejects on stall after the configured timeout; resolves and clears its timer on a real (minimal) zip; importing the module registers no exit listener.
  • The PLATFORMS literal is untouched — the source-text drift guard in codelldb-resolver.test.ts stays green.

Verification

  • Full real vendoring (CODELLDB_FORCE_REBUILD=true CODELLDB_PLATFORMS=win32-x64) → exit 0, "Extracted … in Nms" duration log
  • Pre-push suite: lint, clean build, 3647 unit + 23 integration tests passed

🤖 Generated with Claude Code

…b.js

Fixes #389. A stalled extract-zip promise (yauzl floating-promise design:
resolves only on 'close', rejects only on 'error') left main() forever
pending; the event loop drained and Node exited 0 with no failure output,
bypassing every deliberate failure path.

Three independent layers:
- extractVsixWithWatchdog: Promise.race of extractZip against a timeout
  (default 120s, CODELLDB_EXTRACT_TIMEOUT_MS). The pending timer keeps the
  event loop alive during extraction, and a stall becomes a rejection that
  flows through the existing retry loop and failure summary.
- process.on('exit') guard (registered only when invoked directly): exiting
  0 before main() settles forces exit code 1 with a requested/completed/
  unresolved-platforms diagnostic.
- invokedDirectly now realpaths both sides so a symlinked invocation path
  cannot silently skip main().

Test hooks CODELLDB_TEST_STALL_EXTRACTION / CODELLDB_TEST_SIMULATE_DRAIN
enable network-free regression coverage; new spawn-based tests pin the
whole-process exit-code contract (skip=0, drain=1, summary failure=1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…wn-test timeout

Review follow-ups: extractVsixWithWatchdog takes an opts seam
(extractFn/timeoutMs) so the unit tests inject a stalling extractor
directly instead of routing through a production env hook — the
CODELLDB_TEST_STALL_EXTRACTION variable is gone, and the import tests
drop their stubEnv/re-import gymnastics. CODELLDB_TEST_SIMULATE_DRAIN
stays: the premature-exit guard it exercises is registered only on
direct whole-process invocation, so no import-level seam can reach it.
The spawn-test child timeout drops 60s -> 10s to sit inside the unit
project's 15s testTimeout (a hung child now fails on real evidence
within the test's own budget).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@debugmcpdev
debugmcpdev merged commit 5bc19b7 into main Aug 20, 2026
8 of 9 checks passed
@debugmcpdev
debugmcpdev deleted the fix/389-vendor-exit-guard branch August 20, 2026 21:02
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.

vendor-codelldb.js: intermittent extraction crash exits 0 — failure not propagated

2 participants