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
- Fix the sink/config so the buffer always has at least one stage (default memory buffer)
- Ensure custom sinks initialize their buffer via the standard buffer config APIs
- Re-validate the configuration so sinks with empty buffers are rejected before topology build
- 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
- Always give sinks a default memory buffer stage
- Use the standard buffer config APIs in custom sink implementations
- Keep config validation upstream of topology build so empty buffers fail early
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
- output for default port required for task transforms
- Task started but input has been taken.
- a record with a next ID must have an event count
- a source must always have an external resource
- a valid HTTP/1 URI is valid as an HTTP URI
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)