vectordotdev/vector · error
node exists
Error message
node exists
What it means
`remove_inputs` calls `self.config.inputs_for_node(key).expect("node exists")` (src/topology/running.rs:1009). The running config is expected to contain every node being removed; if the node is absent from the current config's graph, the lookup returns None and panics. The new config lookup is tolerated via `unwrap_or_default`, but the old config lookup is not.
Solutions
- Ensure each component id in the config is unique and removed only once per diff.
- Check that shutdown_diff's removal list is derived from the same config snapshot stored in self.config.
- Use `unwrap_or_default()` symmetry: treat a missing old node as no-op rather than panicking if this state can legitimately occur.
Defensive patterns
Strategy: validation
Validate before calling
// ensure unique component ids before building the config let ids: HashSet<_> = config.components().map(|c| c.id()).collect(); assert_eq!(ids.len(), config.component_count(), "duplicate component keys");
Prevention
- Never reference a component key twice in removal lists.
- Derive removal sets directly from the same config snapshot used by remove_inputs.
- Validate configs with unique ids before reload/shutdown diffs.
When it happens
Trigger: `shutdown_diff` removing a component key that is missing from `self.config` — e.g. a key removed twice in one diff, or a key whose config entry was already mutated away by an earlier reload step.
Common situations: A config where a component was deleted but its key still appears in the removal set (duplicate removal), or hand-edited/duplicated component ids causing inconsistent diff bookkeeping.
Understand the failure class
Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.
Related errors
- transform must exist
- transform output misconfigured
- unknown output
- can't run runner twice
- completing more than 2^16 data files at a time is obviously…
AI-assisted analysis of vectordotdev/vector@bdb87aeaa4 (2026-09-16).
Data as JSON: /api/errors/916dde4fdae36b7a.
Report an issue: GitHub.
Appendix: source
Thrown at src/topology/running.rs:1009
}
}
self.inputs.insert(key.clone(), tx);
new_pieces
.detach_triggers
.remove(key)
.map(|trigger| self.detach_triggers.insert(key.clone(), trigger.into()));
}
fn remove_outputs(&mut self, key: &ComponentKey) {
self.outputs.retain(|id, _output| &id.component != key);
}
fn remove_inputs(&mut self, key: &ComponentKey, diff: &ConfigDiff, new_config: &Config) {
self.inputs.remove(key);
self.detach_triggers.remove(key);
let old_inputs = self.config.inputs_for_node(key).expect("node exists");
let new_inputs = new_config
.inputs_for_node(key)
.unwrap_or_default()
.iter()
.collect::<HashSet<_>>();
for input in old_inputs {
if let Some(output) = self.outputs.get_mut(input) {
if diff.contains(&input.component)
|| diff.is_removed(key)
|| !new_inputs.contains(input)
{
// 3 cases to remove the input:
//
// Case 1: If the input we're removing ourselves from is changing, that means its
// outputs will be recreated, so instead of pausing the sink, we just delete it
// outright to ensure things are clean.
//View on GitHub (pinned to bdb87aeaa4)