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

  1. Inspect recent changes to the `check_for_cycles` loop body for logic that drains `traversal` early.
  2. Avoid mutating the graph concurrently with cycle detection.
  3. 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

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 cycle

View on GitHub (pinned to bdb87aeaa4)