GraphiteEditor/Graphite · error

Encountered invalid node id

Error message

Encountered invalid node id

What it means

While regenerating dependant lists after a network is loaded, this code records every NodeInput::Node reference and then looks up each referenced node with nodes.get_mut(&dep_id). This panic means some input points at a node id that is not present in self.nodes — the graph contains a dangling node reference.

Source

Thrown at node-graph/graph-craft/src/document.rs:833

				node.original_location.dependants = (0..node.implementation.output_count()).map(|_| Vec::new()).collect();
			}
		}
	}

	pub fn populate_dependants(&mut self) {
		let mut dep_changes = Vec::new();
		for (node_id, node) in &mut self.nodes {
			let len = node.original_location.dependants.len();
			node.original_location.dependants.extend(vec![vec![]; (node.implementation.output_count()).max(len) - len]);
			for input in &node.inputs {
				if let NodeInput::Node { node_id: dep_id, output_index, .. } = input {
					dep_changes.push((*dep_id, *output_index, *node_id));
				}
			}
		}
		// println!("{:#?}", self.nodes.get(&NodeId(1)));
		for (dep_id, output_index, node_id) in dep_changes {
			let node = self.nodes.get_mut(&dep_id).expect("Encountered invalid node id");
			let len = node.original_location.dependants.len();
			// One must be added to the index to find the length because indexing in rust starts from 0.
			node.original_location.dependants.extend(vec![vec![]; (output_index + 1).max(len) - len]);
			// println!("{node_id} {output_index} {}", node.implementation.output_count());
			node.original_location.dependants[output_index].push(node_id);
		}
	}

	/// Replace all references in any node of `old_input` with `new_input`
	fn replace_node_inputs(&mut self, node_id: NodeId, old_input: (NodeId, usize), new_input: (NodeId, usize)) {
		let Some(node) = self.nodes.get_mut(&node_id) else { return };
		node.inputs.iter_mut().for_each(|input| {
			if let NodeInput::Node { node_id: input_id, output_index, .. } = input
				&& (*input_id, *output_index) == old_input
			{
				(*input_id, *output_index) = new_input;
			}
		});

View on GitHub (pinned to c507b35645)

Solutions

  1. Re-open the file in the editor version that produced it and re-save, which normalizes the references
  2. Pre-validate before this pass: collect every NodeInput node id and confirm each exists in nodes
  3. When editing graphs in code, route deletions through APIs that remove or rebind referencing inputs instead of mutating the map directly

Example fix

// before
let node = self.nodes.get_mut(&dep_id).expect("Encountered invalid node id");

// after
let Some(node) = self.nodes.get_mut(&dep_id) else {
	log::warn!("skipping dangling reference to node {dep_id}");
	continue;
};
Defensive patterns

Strategy: validation

Validate before calling

fn dangling_references(network: &NodeNetwork) -> Vec<NodeId> {
	let mut missing = Vec::new();
	for node in network.nodes.values() {
		for input in &node.inputs {
			if let NodeInput::Node { node_id: dep_id, .. } = input {
				if !network.nodes.contains_key(dep_id) {
					missing.push(*dep_id);
				}
			}
		}
	}
	missing
}

Prevention

When it happens

Trigger: Documents whose serialized network references a node id that was deleted without rewriting inputs pointing at it; programmatic graph edits (macros, CLI transforms) that remove nodes but leave inputs behind; truncated or hand-edited .graphite/.gdd files.

Common situations: Opening third-party or hand-edited artwork; files saved by editor versions with node-removal bugs; test fixtures built by serializing partial networks.

Related errors


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/792e3969554a94c3. Report an issue: GitHub.