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
- Recompute the index immediately before the mutation, ideally via array.indexOf(node) on the live array
- Mutate from the highest index to the lowest so earlier mutations cannot shift pending indices
- 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
- Prefer indexOf(node) on the live array over cached numeric indices
- Apply array mutations back-to-front so shifts cannot invalidate pending indices
- Assert bounds at your own boundary so failures point at your code, not the helper
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
- Expected parent node to be set on "${target.type}"
- Attempted to insert a statement into `${parentWithType.type}
- Could not find target in array of `${parent.type}.${key}`.
- Expected to find a valid statement parent, but found a paren
- Expected to find the target "${target.type}" on the "${resul
AI-assisted analysis of facebook/flow@d1341dac89 (2026-08-17).
Data as JSON: /api/errors/964cd7a6f1d62d3b.
Report an issue: GitHub.