vectordotdev/vector · error
can't be empty
Error message
can't be empty
What it means
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.
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.
Defensive patterns
Strategy: try-catch
Try / catch
// This is an internal invariant; guard against it by not mutating the graph during checks: let graph = Arc::new(graph); // share immutably let result = std::panic::catch_unwind(|| graph.check_for_cycles());
Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Modifying the graph while cycle detection runs (async test harnesses), or hand-editing the traversal logic so the deque can be emptied mid-iteration.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
AI-assisted analysis of vectordotdev/vector@bdb87aeaa4 (2026-09-16).
Data as JSON: /api/errors/5aeca7e6957e14c1.
Report an issue: GitHub.
Appendix: source
Thrown at src/config/graph.rs:284
}
}
pub fn check_for_cycles(&self) -> Result<(), String> {
// find all sinks
let sinks = self.nodes.iter().filter_map(|(name, node)| match node {
Node::Sink { .. } => Some(name),
_ => None,
});
// run DFS from each sink while keep tracking the current stack to detect cycles
for s in sinks {
let mut traversal: VecDeque<ComponentKey> = VecDeque::new();
let mut visited: HashSet<ComponentKey> = HashSet::new();
let mut stack: IndexSet<ComponentKey> = IndexSet::new();
traversal.push_back(s.to_owned());
while !traversal.is_empty() {
let n = traversal.back().expect("can't be empty").clone();
if !visited.contains(&n) {
visited.insert(n.clone());
stack.insert(n.clone());
} else {
// we came back to the node after exploring all its children - remove it from the stack and traversal
stack.shift_remove(&n);
traversal.pop_back();
}
let inputs = self
.edges
.iter()
.filter(|e| e.to == n)
.map(|e| e.from.clone());
for input in inputs {
if !visited.contains(&input.component) {
traversal.push_back(input.component);
} else if stack.contains(&input.component) {
// we reached the node while it is on the current stack - it's a cycleView on GitHub (pinned to bdb87aeaa4)