{"record":{"id":"5aeca7e6957e14c1","repo":"vectordotdev/vector","slug":"can-t-be-empty","errorCode":null,"errorMessage":"can't be empty","messagePattern":"can't be empty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/config/graph.rs","lineNumber":284,"sourceCode":"        }\n    }\n\n    pub fn check_for_cycles(&self) -> Result<(), String> {\n        // find all sinks\n        let sinks = self.nodes.iter().filter_map(|(name, node)| match node {\n            Node::Sink { .. } => Some(name),\n            _ => None,\n        });\n\n        // run DFS from each sink while keep tracking the current stack to detect cycles\n        for s in sinks {\n            let mut traversal: VecDeque<ComponentKey> = VecDeque::new();\n            let mut visited: HashSet<ComponentKey> = HashSet::new();\n            let mut stack: IndexSet<ComponentKey> = IndexSet::new();\n\n            traversal.push_back(s.to_owned());\n            while !traversal.is_empty() {\n                let n = traversal.back().expect(\"can't be empty\").clone();\n                if !visited.contains(&n) {\n                    visited.insert(n.clone());\n                    stack.insert(n.clone());\n                } else {\n                    // we came back to the node after exploring all its children - remove it from the stack and traversal\n                    stack.shift_remove(&n);\n                    traversal.pop_back();\n                }\n                let inputs = self\n                    .edges\n                    .iter()\n                    .filter(|e| e.to == n)\n                    .map(|e| e.from.clone());\n                for input in inputs {\n                    if !visited.contains(&input.component) {\n                        traversal.push_back(input.component);\n                    } else if stack.contains(&input.component) {\n                        // we reached the node while it is on the current stack - it's a cycle","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/vectordotdev/vector/blob/bdb87aeaa4c4ff27c0ba643c1c77b21bf2ef4013/src/config/graph.rs#L266-L302","documentation":"In `check_for_cycles`, the DFS pops from a `traversal` deque inside `while !traversal.is_empty()`, asserting the deque is non-empty. Since the loop condition guarantees this, the panic can only occur if the deque was mutated unexpectedly — an internal invariant guard during cycle detection.","triggerScenarios":"Calling `check_for_cycles` (directly or via paths_doesnt_detect_noncycles / get_relevant_test_components) on a graph; a panic indicates concurrent or re-entrant mutation of the traversal deque, i.e. a code bug rather than user input.","commonSituations":"Modifying the graph while cycle detection runs (async test harnesses), or hand-editing the traversal logic so the deque can be emptied mid-iteration.","solutions":["Inspect recent changes to the `check_for_cycles` loop body for logic that drains `traversal` early.","Avoid mutating the graph concurrently with cycle detection.","If reproducible on stock code, report upstream with the config that triggered it."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// This is an internal invariant; guard against it by not mutating the graph during checks:\nlet graph = Arc::new(graph);  // share immutably\nlet result = std::panic::catch_unwind(|| graph.check_for_cycles());","preventionTips":["Never mutate a ConfigGraph concurrently with cycle detection.","Build the graph fully before running topology checks.","Keep the traversal-loop logic untouched in refactors; add unit tests over cyclic and acyclic graphs."],"tags":["graph","cycle-detection","invariant-violation"],"backgroundTag":"internal-invariant-violation","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"}