facebook/relay · error · Error

NormalizationEngine: Expected path for @stream to end in a p

Error message

NormalizationEngine: Expected path for @stream to end in a positive integer index, got `${String(finalPathEntry)}`

What it means

@stream payloads are identified by their position in a list; the last segment of the payload's path must be the integer index of the streamed item. Relay parses the final path entry and requires it to be a non-negative integer that round-trips (parseInt equal to the original). If the path ends in anything else (a field name, a string, -1), Relay cannot determine the list position and throws.

Source

Thrown at packages/relay-runtime/store/NormalizationEngine.js:513

    const prevIDs = RelayModernRecord.getLinkedRecordIDs(
      parentRecord,
      storageKey,
    );
    if (prevIDs == null) {
      throw err(
        'NormalizationEngine: Expected record `' +
          parentID +
          '` to have fetched field `' +
          field.name +
          '` with @stream.',
      );
    }

    const finalPathEntry = path[path.length - 1];
    const itemIndex = parseInt(finalPathEntry, 10);
    if (itemIndex !== finalPathEntry || itemIndex < 0) {
      throw err(
        'NormalizationEngine: Expected path for @stream to end in a ' +
          'positive integer index, got `' +
          String(finalPathEntry) +
          '`',
      );
    }

    const typeName = field.concreteType ?? (data as $FlowFixMe).__typename;
    if (typeof typeName !== 'string') {
      throw err(
        'NormalizationEngine: Expected @stream field `' +
          field.name +
          '` to have a __typename.',
      );
    }

    const getDataID = this._options.getDataID;
    const itemID =

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Inspect the incremental payload's `path` in your network layer; ensure the final element is the numeric list index
  2. Fix or replace the middleware/proxy that is rewriting or dropping path segments
  3. If building payloads in tests, construct path as [...fieldPath, itemIndex] with a real number as the last entry
  4. Verify the server actually supports @stream/multipart incremental delivery and emits conformant paths

Example fix

// before
{ path: ["node", "comments"] }
// after
{ path: ["node", "comments", 2] }
Defensive patterns

Strategy: validation

Validate before calling

const last = payload.path[payload.path.length - 1];
if (typeof last !== 'number' || !Number.isInteger(last) || last < 0) {
  throw new Error(`Invalid @stream path terminator: ${String(last)}`);
}

Type guard

function hasNumericIndexPath(p: unknown): p is (string | number)[] {
  return Array.isArray(p) && typeof p[p.length - 1] === 'number' && Number.isInteger(p[p.length - 1]);
}

Prevention

When it happens

Trigger: Normalizing a @stream incremental payload whose `path` array does not end in a numeric index — e.g. a malformed incremental response from the server, a network layer that mangles the incremental path, or a custom router/gateway rewriting paths (string indices like "01" would also fail since parseInt yields 1 !== "01").

Common situations: Proxy or cache layers (e.g. persisted-query gateways, Cloudflare workers) re-serializing incremental delivery payloads, custom RelayNetwork implementations that build paths incorrectly, or testing harnesses fabricating incremental payloads with wrong paths.

Related errors


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