mrdoob/three.js · error
THREE.NodeBuilder: Invalid node chaining!
Error message
THREE.NodeBuilder: Invalid node chaining!
What it means
An internal NodeBuilder invariant: `removeChain(node)` pops the last entry of the `chaining` stack and asserts it is the same node passed in. A mismatch means the node-graph traversal pushed and popped chain entries out of order. This is almost always a library or custom-Node bug in `setup()`/`generate()` that calls builder flow methods asymmetrically, not a user configuration error.
Source
Thrown at src/nodes/core/NodeBuilder.js:960
}
*/
this.chaining.push( node );
}
/**
* Removes the given node from the internal node chain.
*
* @param {Node} node - The node to remove.
*/
removeChain( node ) {
const lastChain = this.chaining.pop();
if ( lastChain !== node ) {
throw new Error( 'THREE.NodeBuilder: Invalid node chaining!' );
}
}
/**
* Returns the native shader method name for a given generic name. E.g.
* the method name `textureDimensions` matches the WGSL name but must be
* resolved to `textureSize` in GLSL.
*
* @abstract
* @param {string} method - The method name to resolve.
* @return {string} The resolved method name.
*/
getMethod( method ) {
return method;
View on GitHub (pinned to da05705fa3)
Solutions
- Identify the custom Node whose setup/generate is unbalanced and ensure every code path returns a single node (no early throws that skip cleanup).
- Avoid sharing a single node instance across multiple materials/renderers; clone it.
- Reproduce against the previous Three.js version to detect a regression, then bisect the node graph.
- Report as a library bug if it reproduces with only built-in nodes.
Defensive patterns
Strategy: try-catch
Try / catch
// This is a library/custom-node invariant; wrap the build to capture and report.
try {
renderer.compileAsync( scene, camera );
} catch ( e ) {
if ( /Invalid node chaining/.test( e.message ) ) {
console.error( 'Unbalanced node chain — likely a custom Node setup()/generate() bug', e );
} else throw e;
} Prevention
- In custom nodes, ensure every setup()/generate() path returns a node and never throws mid-flow leaving the chain pushed.
- Never share a node instance across concurrent builds; clone it.
- Bisect against the previous Three.js version to detect regressions.
When it happens
Trigger: A custom Node whose `generate()`/`setup()` returns early or throws partway, leaving the chain unbalanced; a node that manually calls `builder.addChain()` without a matching `removeChain()`; corrupted node reuse across builders; race conditions from sharing mutable node instances between simultaneous builds.
Common situations: Authoring a custom TSL node and forgetting to return the proper node from an `Fn` body; mutating a node tree while it is being built; cloning/caching a node that holds builder-specific chain state; version regression after upgrading Three.js node internals.
Related errors
- THREE.NodeBuilder: Type '${type}' not found in generate cons
- THREE.NodeBuilder: Invalid active stack removal.
- THREE.NodeBuilder: Uniform "${ type }" not implemented.
AI-assisted analysis of mrdoob/three.js@da05705fa3 (2026-08-12).
Data as JSON: /api/errors/d580f38340e39b10.
Report an issue: GitHub.