Skip to content

fix(server): install uncaught error handlers for library consumers#505

Open
Alexander-Ollman wants to merge 1 commit into
rynfar:mainfrom
Alexander-Ollman:fix/library-process-error-handlers
Open

fix(server): install uncaught error handlers for library consumers#505
Alexander-Ollman wants to merge 1 commit into
rynfar:mainfrom
Alexander-Ollman:fix/library-process-error-handlers

Conversation

@Alexander-Ollman
Copy link
Copy Markdown

Summary

bin/cli.ts has long installed process.on('uncaughtException') and process.on('unhandledRejection') handlers that log and swallow socket-level errors (EPIPE, ECONNRESET) and SDK subprocess crashes instead of letting them kill the proxy. Until this PR those handlers lived only in the CLI, so library consumers calling startProxyServer in-process had no safety net.

In our setup (era-code starts meridian via await import('@rynfar/meridian') and startProxyServer({...}) so it can run inside the same Node process as the OpenCode wrapper) a single mid-stream EPIPE was crashing the host process and orphaning OpenCode, which then saw ECONNRESET on the in-flight request, then ConnectionRefused on retries, and entered an unbounded retry loop with Cannot connect to API: Unable to connect… until the user killed it.

This PR moves the safety net into the library entry behind an opt-in flag, so any library consumer can enable it (and the bundled CLI already does).

Changes

  • src/proxy/types.ts — add installProcessErrorHandlers?: boolean to ProxyConfig. Defaults to false to preserve any handlers a library consumer has already installed.
  • src/proxy/server.ts — export installProxyProcessErrorHandlers() (idempotent). startProxyServer calls it when installProcessErrorHandlers: true is passed.
  • bin/cli.ts — passes installProcessErrorHandlers: true into start({...}) and removes the now-duplicate handler block. The CLI's behavior is byte-identical to before; the handlers are now installed in exactly one place.
  • src/__tests__/install-process-error-handlers.test.ts — verifies the helper attaches both listeners and is idempotent on repeat calls.

Test plan

  • bun test src/__tests__/install-process-error-handlers.test.ts — 2 pass
  • bun build bin/cli.ts src/proxy/server.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --entry-naming '[name].js' succeeds; both dist/cli.js and dist/server.js parse cleanly with node --check
  • Manual: confirmed installProxyProcessErrorHandlers symbol present in the built dist/server.js bundle
  • Maintainer: full bun test to confirm no regressions in adjacent suites

Compatibility

  • Existing CLI users: identical behavior — handlers still installed, same log format ([PROXY] Uncaught exception (recovered): …).
  • Existing library consumers: identical behavior unless they explicitly pass installProcessErrorHandlers: true (or call the new exported helper). No silent change to global process state.

…r library consumers

The CLI (bin/cli.ts) has long installed process-level handlers that log and
swallow socket-level errors (EPIPE, ECONNRESET) and SDK subprocess crashes
instead of letting them kill the proxy. Until now those handlers lived only
in the CLI, so library consumers — e.g. era-code calling startProxyServer
in-process — had no safety net: a single mid-stream EPIPE crashed the host
process and orphaned downstream agents (which then saw ECONNRESET, then
ConnectionRefused, and entered an unbounded retry loop).

Lift the safety net into the library entry behind an opt-in flag so existing
library consumers' handler choices are preserved by default.

Changes
- src/proxy/types.ts: add `installProcessErrorHandlers?: boolean` to ProxyConfig
- src/proxy/server.ts: export `installProxyProcessErrorHandlers()` (idempotent);
  startProxyServer calls it when the config flag is true
- bin/cli.ts: pass `installProcessErrorHandlers: true` and remove the duplicate
  handler block — single source of truth
- src/__tests__/install-process-error-handlers.test.ts: verifies the helper
  attaches both listeners and is idempotent on repeat calls
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.

1 participant