facebook/relay · error

RelayObservable: Expected pollInterval to be positive, got:

Error message

RelayObservable: Expected pollInterval to be positive, got: ${pollInterval}

What it means

`RelayObservable.prototype.poll(interval)` requires a strictly positive numeric millisecond interval. This dev-mode check throws when the interval is not a number or is <= 0, because polling with a non-positive interval would loop without any delay and starve the event loop.

Source

Thrown at packages/relay-runtime/network/RelayObservable.js:417

      });

      return () => {
        subscriptions.forEach(sub => sub.unsubscribe());
        subscriptions.length = 0;
      };
    });
  }

  /**
   * Returns a new Observable which first mirrors this Observable, then when it
   * completes, waits for `pollInterval` milliseconds before re-subscribing to
   * this Observable again, looping in this manner until unsubscribed.
   *
   * The returned Observable never completes.
   */
  poll(pollInterval: number): RelayObservable<T> {
    if (__DEV__) {
      if (typeof pollInterval !== 'number' || pollInterval <= 0) {
        throw new Error(
          'RelayObservable: Expected pollInterval to be positive, got: ' +
            pollInterval,
        );
      }
    }
    return RelayObservable.create(sink => {
      let subscription;
      let timeout;
      const poll = () => {
        subscription = this.subscribe({
          next: sink.next,
          error: sink.error,
          complete() {
            timeout = setTimeout(poll, pollInterval);
          },
        });
      };

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Pass a positive number in milliseconds: `poll(1000)`
  2. Guard config values: `pollInterval > 0 ? pollInterval : DEFAULT_INTERVAL`
  3. Fix env parsing with a fallback: `Number(process.env.POLL_MS) || 30000`
  4. If you don't want polling, don't call `poll` at all — use `create` or `fromPromise` instead

Example fix

// before
const interval = Number(config.pollSeconds); // 0 or NaN
observable.poll(interval);
// after
const interval = (Number(config.pollSeconds) || 30) * 1000;
observable.poll(interval);
Defensive patterns

Strategy: validation

Validate before calling

function safePoll(obs, intervalMs) {
  if (typeof intervalMs !== 'number' || !(intervalMs > 0)) {
    throw new TypeError(`pollInterval must be a positive number, got: ${intervalMs}`);
  }
  return obs.poll(intervalMs);
}

Type guard

function isValidPollInterval(v: unknown): v is number {
  return typeof v === 'number' && Number.isFinite(v) && v > 0;
}

Try / catch

try {
  observable.poll(interval);
} catch (e) {
  if (e.message.includes('pollInterval to be positive')) {
    observable.poll(DEFAULT_POLL_INTERVAL_MS);
  } else throw e;
}

Prevention

When it happens

Trigger: `source.poll(0)`, `source.poll(-1000)`, or `source.poll(undefined/null/NaN)` — typically when the interval comes from a config value or env var parsed incorrectly.

Common situations: Config defaulting to 0 for "no polling"; `Number(process.env.POLL_MS)` producing NaN; unit confusion (seconds vs milliseconds, e.g. passing 0.5 seconds instead of 500).

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/9211914cf35e4414. Report an issue: GitHub.