{"record":{"id":"916dde4fdae36b7a","repo":"vectordotdev/vector","slug":"node-exists","errorCode":null,"errorMessage":"node exists","messagePattern":"node exists","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/topology/running.rs","lineNumber":1009,"sourceCode":"            }\n        }\n\n        self.inputs.insert(key.clone(), tx);\n        new_pieces\n            .detach_triggers\n            .remove(key)\n            .map(|trigger| self.detach_triggers.insert(key.clone(), trigger.into()));\n    }\n\n    fn remove_outputs(&mut self, key: &ComponentKey) {\n        self.outputs.retain(|id, _output| &id.component != key);\n    }\n\n    fn remove_inputs(&mut self, key: &ComponentKey, diff: &ConfigDiff, new_config: &Config) {\n        self.inputs.remove(key);\n        self.detach_triggers.remove(key);\n\n        let old_inputs = self.config.inputs_for_node(key).expect(\"node exists\");\n        let new_inputs = new_config\n            .inputs_for_node(key)\n            .unwrap_or_default()\n            .iter()\n            .collect::<HashSet<_>>();\n\n        for input in old_inputs {\n            if let Some(output) = self.outputs.get_mut(input) {\n                if diff.contains(&input.component)\n                    || diff.is_removed(key)\n                    || !new_inputs.contains(input)\n                {\n                    // 3 cases to remove the input:\n                    //\n                    // Case 1: If the input we're removing ourselves from is changing, that means its\n                    // outputs will be recreated, so instead of pausing the sink, we just delete it\n                    // outright to ensure things are clean.\n                    //","sourceCodeStart":991,"sourceCodeEnd":1027,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/src/topology/running.rs#L991-L1027","documentation":"`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.","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// ensure unique component ids before building the config\nlet ids: HashSet<_> = config.components().map(|c| c.id()).collect();\nassert_eq!(ids.len(), config.component_count(), \"duplicate component keys\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["rust","topology","shutdown","config"],"backgroundTag":"entity-not-found","analyzedSha":"bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013","analyzedAt":"2026-09-16T02:53:35.741Z","contentChangedAt":"2026-09-16T02:53:35.741Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}