facebook/flow · error · Error

Invalid Mutation: Tried to mutate an elements array with an

Error message

Invalid Mutation: Tried to mutate an elements array with an out of bounds index. Index: ${index}, Array Size: ${array.length}

What it means

astArrayMutationHelpers wraps every array-mutating helper (replace/insert/remove at index) with assertArrayBounds, which requires 0 <= index < array.length. The throw message reports both the offending index and the current array size, so a size smaller than expected is itself a clue: the array was already mutated. It prevents silent no-ops or undefined holes in AST arrays.

Source

Thrown at packages/flow-parser/oxidized-src/transform/astArrayMutationHelpers.js:13

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict
 * @format
 */

function assertArrayBounds<T>(array: ReadonlyArray<T>, index: number): void {
  if (index < 0 || index >= array.length) {
    throw new Error(
      `Invalid Mutation: Tried to mutate an elements array with an out of bounds index. Index: ${index}, Array Size: ${array.length}`,
    );
  }
}

export function arrayIsEqual(
  a1: ReadonlyArray<unknown>,
  a2: ReadonlyArray<unknown>,
): boolean {
  if (a1 === a2) {
    return true;
  }

  if (a1.length !== a2.length) {
    return false;
  }

  for (let i = 0; i < a1.length; i++) {

View on GitHub (pinned to d1341dac89)

Solutions

  1. Recompute the index immediately before the mutation, ideally via array.indexOf(node) on the live array
  2. Mutate from the highest index to the lowest so earlier mutations cannot shift pending indices
  3. Add a bounds assert in your own code comparing index against the array length at call time

Example fix

// before
const idx = stmts.indexOf(target);
// ...other mutations shrink stmts...
replaceInArray(stmts, idx, newNode);

// after
const idx = stmts.indexOf(target); // recompute right before use
if (idx < 0 || idx >= stmts.length) throw new Error('stale index');
replaceInArray(stmts, idx, newNode);
Defensive patterns

Strategy: validation

Validate before calling

function assertInBounds(array, index) {
  if (!Number.isInteger(index) || index < 0 || index >= array.length) {
    throw new RangeError('Index ' + index + ' invalid for array of ' + array.length);
  }
}
assertInBounds(stmts, idx);

Type guard

const inBounds = (array, index) =>
  Number.isInteger(index) && index >= 0 && index < array.length;

Try / catch

try {
  replaceInArray(arr, idx, node);
} catch (e) {
  if (e.message.includes('out of bounds index')) {
    idx = arr.indexOf(nodeToFind); // re-resolve against the live array, retry once
  } else throw e;
}

Prevention

When it happens

Trigger: Calling an array mutation helper with a negative index, an index equal to length, or an index computed against an older, longer version of the same array after earlier mutations shrank it.

Common situations: Codemods that snapshot indices up front and apply mutations in a loop (each removal shifts subsequent indices); off-by-one bugs using array.length as an insert position instead of the valid range for the specific helper.

Related errors


AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17). Data as JSON: /api/errors/964cd7a6f1d62d3b. Report an issue: GitHub.