Skip to content

fix: delay process.exit on Windows to dodge libuv UV_HANDLE_CLOSING crash - #228

Merged
sgoedecke merged 4 commits into
actions:mainfrom
mickey-mikey:fix/libuv-windows-exit
May 25, 2026
Merged

sgoedecke merged 4 commits into
actions:mainfrom
mickey-mikey:fix/libuv-windows-exit

Conversation

@mickey-mikey

@mickey-mikey mickey-mikey commented May 24, 2026 •

Copy link
Copy Markdown
Contributor

Closes #227.

What

Extract a safeExit(code) helper that yields 100ms on Windows before calling process.exit(code), and route both existing process.exit calls through it. Behaviour on Linux and macOS is unchanged (no setTimeout in their code path).

Why

The two explicit process.exit() calls in run() abort with the libuv UV_HANDLE_CLOSING assertion on Windows when the openai SDK's undici-based HTTP/2 connection pool is still tearing down the TLS socket to models.github.ai. Documented upstream Node bug at nodejs/node#56645 (open since Jan 2025; the fix PR #61999 has been stalled in review and no released Node version contains it). See #227 for the full diagnosis and reproducer.

The 100ms setTimeout is the same workaround already shipped by astral-sh/setup-uv#880, silverwind/updates#138 (200ms), dmno-dev/varlock#600, and others tracking the same Node issue.

Verification

Local repro on Windows (Node 24.15.0 bundled with actions-runner v2.334.0, openai SDK against models.github.ai, process.exit(0) immediately after the call):

  • Without the patch: 5/5 trials crashed with the UV_HANDLE_CLOSING assertion (exit 0xC0000409).

  • With the patch: 5/5 trials passed.

End-to-end CI on a private repo across self-hosted Linux, macOS, and Windows runners (single run each):

  • actions/ai-inference@v2.1.0 unpatched: Windows leg crashed at the ai-inference step; Linux and macOS passed.

  • Fork pinned to this PR's commit: all three legs passed.

Notes

  • The helper is async and returns Promise<never>. Both existing call sites are already in async context so callers become await safeExit(...) instead of process.exit(...).

  • silverwind tried dropping the explicit process.exit (let the event loop drain) in their #137 and rolled back to the setTimeout in #138 — the drain approach did not hold up on Node 24 Windows.

  • 100ms matches setup-uv's current value (originally 50ms in #689, raised to 100ms in #880). silverwind uses 200ms.

@mickey-mikey
mickey-mikey requested a review from a team as a code owner May 24, 2026 05:59
…rash

On Windows, the explicit process.exit() calls in run() abort with
  Assertion failed: !(handle->flags & UV_HANDLE_CLOSING),
  file src\win\async.c, line 76
when the openai SDK's undici-based HTTP/2 connection pool is still
tearing down the TLS socket to models.github.ai. This is the upstream
Node bug tracked at nodejs/node#56645 — Node calls uv_async_send() on
an async handle after uv_close(), which libuv asserts against. The
upstream fix (nodejs/node#61999) has been stalled in review since
January 2025, and no released Node version contains it yet.

Reproduces 100% of the time against models.github.ai on Windows Node 24
with the openai SDK; passes 100% with a brief setTimeout before exit.
Same workaround already shipped by astral-sh/setup-uv (#880, 100ms) and
silverwind/updates (#138, 200ms).

Extract a `safeExit(code)` helper that yields for 100ms on Windows only
before calling process.exit; Linux and macOS take the original code
path with no added latency.

Refs:
- nodejs/node#56645
- astral-sh/setup-uv#880
- silverwind/updates#138
@mickey-mikey
mickey-mikey force-pushed the fix/libuv-windows-exit branch from 4593b45 to 0bb3dae Compare May 24, 2026 06:12
@sgoedecke

Copy link
Copy Markdown
Contributor

@mickey-mikey Thanks for putting up the PR. Happy to approve once licensed cache is updated and linting errors are fixed up.

mickey-mikey and others added 3 commits May 25, 2026 10:07
Regenerated via `licensed cache` to clear 22 out-of-date dependency
records and 2 stale entries (@fastify/busboy, safe-buffer) flagged by
the Licensed CI check. New entry: ip-address.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- VALIDATE_BIOME_FORMAT/LINT: false — Biome's defaults (tabs, double
  quotes, semicolons) conflict with the project's Prettier+ESLint
  toolchain. Follows the same one-toolchain-per-concern pattern as the
  existing VALIDATE_JAVASCRIPT_STANDARD/TYPESCRIPT_STANDARD disables.
- dependabot.yml cooldown: 5 days — resolves the zizmor
  dependabot-cooldown warnings on both ecosystems.

Trivy still reports transitive-dep CVEs (mostly hono/express subgraph);
those need a focused dependency-update PR, not bundling into this fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Non-breaking transitive-dep updates (package.json unchanged). Reduces
Trivy's reported CVE count from 18 to a smaller residual that requires
`--force` and breaking changes.

dist/ rebundled to pick up the patched deps. All 105 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mickey-mikey

Copy link
Copy Markdown
Contributor Author

@sgoedecke, I did what I can, but I think I might need some help. I'm getting errors that might require breaking changes from Trivy. Could you take a look?

@sgoedecke
sgoedecke merged commit a780588 into actions:main May 25, 2026
4 of 7 checks passed
@sgoedecke

Copy link
Copy Markdown
Contributor

@mickey-mikey Thanks for trying. There are some CI issues here that I don't think we need to resolve as part of this PR. Approved, merged, cut a new release.

@mickey-mikey
mickey-mikey deleted the fix/libuv-windows-exit branch May 25, 2026 23:30
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.

process.exit() in main.ts triggers UV_HANDLE_CLOSING libuv crash on Windows (nodejs/node#56645)

2 participants