facebook/flow · error · InvalidReplacementError
Expected to find the
Error message
Expected to find the ${target.type} as a direct child of the ${parent.type}. What it means
InvalidReplacementError thrown by flow-transform's replaceNode when locating the target: it walks every key of the parent and looks for the target as a direct child (by identity, or via getOriginalNode for detached nodes). If the target is not a direct child of its parent - it is deeper in the tree, already replaced, or a stale node object - the lookup fails and throws.
Solutions
- Call replaceNode on the node itself (the mutation derives the parent) and only once per node per run.
- After any replace, continue mutating from the replacement node, not the original.
- Use `replaceNodeWithContext` when you need the surrounding context to stay consistent.
- Never reuse node objects across separate transform() invocations.
Example fix
// before mutate.replaceNode(oldTarget, replacement); // oldTarget already replaced earlier - throws // after - mutate the current tree's node mutate.replaceNode(currentTarget, replacement);
Defensive patterns
Strategy: validation
Validate before calling
function isDirectChild(parent, target) {
return Object.values(parent).some(v =>
v === target || (Array.isArray(v) && v.includes(target)),
);
}
if (isDirectChild(node.parent, node)) {
mutate.replaceNode(node, replacement);
} Try / catch
try {
mutate.replaceNode(node, replacement);
} catch (e) {
if (e.name === 'InvalidReplacementError' && /Expected to find/.test(e.message)) {
// node is stale (already replaced) - re-run the traversal on current AST
} else throw e;
} Prevention
- One replacement per node per run.
- Continue from the replacement node after a replace.
- Never cache nodes across transform() calls.
When it happens
Trigger: `mutate.replaceNode(node, replacement)` after an earlier mutation in the same run replaced or removed `node` or its parent; passing a node whose `parent` pointer points at a different (old) subtree; replacing a node captured from a previous transform() call.
Common situations: Chained codemod operations where a replace happens on a node that a previous visitor already replaced; holding onto nodes between separate transform runs; replacing a node found via scope analysis after the tree was mutated.
Related errors
- Could not find target in array of
- Could not find target in array of
- Expected to find the target
- Attempted to insert a statement into
- Cannot perform a remove mutation on node of type
AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20).
Data as JSON: /api/errors/bbf1edd71d448789.
Report an issue: GitHub.
Appendix: source
Thrown at packages/flow-transform/src/transform/mutations/ReplaceNode.js:109
const child = (parent as $FlowFixMe)[key];
if (isNode(child)) {
// $FlowFixMe[invalid-compare]
if (child === target) {
return {type: 'single', parent, key};
}
} else if (Array.isArray(child)) {
for (let i = 0; i < child.length; i += 1) {
const current = child[i];
const originalNode = getOriginalNode(current);
if (current === target || originalNode === target) {
return {type: 'array', parent, key, targetIndex: i};
}
}
}
}
// this shouldn't happen ever
throw new InvalidReplacementError(
`Expected to find the ${target.type} as a direct child of the ${parent.type}.`,
);
}
View on GitHub (pinned to f88ac94bcf)