vectordotdev/vector · critical

cant ever be empty

Error message

cant ever be empty

What it means

While building a sink's buffer, the topology builder reads sink.buffer.stages().first() and .expect()s the stage list to be non-empty. A sink always has at least one buffer stage (validated at config time), so hitting this panic means an internal invariant was violated — the sink's buffer was accessed after its stages were removed/reset.

Solutions

  1. Fix the sink/config so the buffer always has at least one stage (default memory buffer)
  2. Ensure custom sinks initialize their buffer via the standard buffer config APIs
  3. Re-validate the configuration so sinks with empty buffers are rejected before topology build
  4. Report as a bug if a stock sink config triggers it — this is an internal invariant violation

Example fix

// before
BufferStage list: [] (empty stages passed to sink buffer)
// after
buffer: { stages: [{ type: "memory", ... }] }  // or default buffer
Defensive patterns

Strategy: type-guard

Validate before calling

// config-time guard: every sink must declare at least one buffer stage
assert!(!sink.buffer.stages().is_empty(), "sink {} has no buffer stages", key);

Type guard

fn has_buffer_stage(sink: &SinkConfig) -> bool { !sink.buffer.stages().is_empty() }

Prevention

When it happens

Trigger: Programmatic or corrupted configuration where a sink reaches build_sinks without any buffer stages; bugs in custom sink implementations that construct an empty BufferStage list; config altered between validation and build.

Common situations: Developers adding new sink types who forget to give the buffer a default stage; hand-edited or generated configs bypassing config validation; version-skew between config serialization and runtime expectations.

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/1daf5ce65dbdd8b3. Report an issue: GitHub.

Appendix: source

Thrown at src/topology/builder.rs:689

        let enable_healthcheck = healthcheck.enabled && self.config.healthchecks.enabled;
        let healthcheck_timeout = healthcheck.timeout;

        let typetag = sink.inner.get_component_name();
        let input_type = sink.inner.input().data_type();

        // At this point, we've validated that all transforms are valid, including any
        // transform that mutates the schema provided by their sources. We can now validate the
        // schema expectations of each individual sink.
        if let Err(mut err) =
            schema::validate_sink_expectations(key, sink, self.config, enrichment_tables.clone())
        {
            self.errors.append(&mut err);
        };

        let (tx, rx) = match self.buffers.remove(key) {
            Some(buffer) => buffer,
            _ => {
                let buffer_type = match sink.buffer.stages().first().expect("cant ever be empty") {
                    BufferType::Memory { .. } => "memory",
                    BufferType::DiskV2 { .. } => "disk",
                };
                let buffer_span = error_span!("sink", buffer_type);
                let buffer = sink
                    .buffer
                    .build(
                        self.config.global.data_dir.clone(),
                        key.to_string(),
                        buffer_span,
                    )
                    .await;
                match buffer {
                    Err(error) => {
                        self.errors.push(format!("Sink \"{key}\": {error}"));
                        return;
                    }
                    Ok((tx, rx)) => (tx, Arc::new(Mutex::new(Some(rx.into_stream())))),

View on GitHub (pinned to bdb87aeaa4)