Skip to content

fix(feature-flags): Keep polling when no error listener is attached - #1728

Open
stanleyphu wants to merge 2 commits into
mainfrom
fix/runtime-client-unhandled-error
Open

stanleyphu wants to merge 2 commits into
mainfrom
fix/runtime-client-unhandled-error

Conversation

@stanleyphu

@stanleyphu stanleyphu commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Why

A runtime client with no error listener crashes the host process on its first failed poll. poll() runs from a timer, and emitting error with nothing listening throws, because the emitter follows Node's EventEmitter. The throw escapes as an unhandled rejection and skips the retry. On a 401 it also skips failed and the waitUntilReady() rejection, so callers' try/catch around waitUntilReady() never runs. #1630 called this out as a pre-existing follow-up. Every example in the runtime client docs except the events one creates a client without an error listener, and so does getFeatureFlagsRuntimeClient() in authkit-nextjs.

  • The poll loop emits error only when a listener is attached. With no listener and no logger, it warns once through workos.emitWarning and keeps retrying, so failures still aren't silent. A 401 always gets its own warning that syncing stopped, even after an earlier failure warned.
  • 401 handling is unchanged: failed fires, polling stops, and waitUntilReady() rejects with UnauthorizedException before initialization.
  • Calling client.emit('error') directly with no listener still throws.

Merging this before the release PR (#1722) ships it in 12.0.0.

Not covered by CI

  • Ran the runtime client guide's main example, unchanged (no error listener, try/catch around waitUntilReady()), against builds of main and this branch, with scripted API responses.
  • The four new tests fail on main: polling stops after an unobserved failure, waitUntilReady() never rejects on a 401, and nothing warns. They pass here.

Proof

main, 401 on the first poll:
  UnauthorizedException thrown from the poll timer; the process exits and the example's catch never runs

this branch, 401 on the first poll:
  Runtime client failed to initialize: UnauthorizedException: Could not authorize the request. Maybe your API key is invalid?

main, one poll fails all four attempts:
  the process exits with an unhandled rejection

this branch, same:
  (node) WorkOS: Feature flag sync failed and no 'error' listener or logger is attached; keeping the last good flags and retrying. ...
  later polls return 200, and the process is still running 2 seconds later

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@stanleyphu

Copy link
Copy Markdown
Contributor Author

@greptileai review this draft

@stanleyphu
stanleyphu marked this pull request as ready for review October 19, 2026 22:53
@stanleyphu
stanleyphu requested review from a team as code owners October 19, 2026 22:53
@stanleyphu
stanleyphu requested review from Deborah-Digges and hadihallak and removed request for hadihallak October 19, 2026 22:53
@greptile-apps

greptile-apps Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge, with no outstanding findings.

Summary

Keeps background polling alive when no error listener is attached.

  • Flag polling reports unobserved errors and preserves retries and 401 stops.

The previous unnumbered finding is fixed: the 401 warning now says syncing stopped rather than promising retries.

Reviews (2) · Last reviewed commit: "fix(feature-flags): Warn when a 401 stop..." · Reviewed by Greptile

Comment thread src/feature-flags/runtime-client.ts Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@stanleyphu

Copy link
Copy Markdown
Contributor Author

@greptileai review

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants