vectordotdev/vector · error
Every ContainerLogInfo has it's ContainerState
Error message
Every ContainerLogInfo has it's ContainerState
What it means
docker_logs runs an internal actor that owns containers: HashMap<ContainerId, ContainerState>. When a container's log-stream future finishes it returns its ContainerLogInfo to the actor, which resolves the entry with containers.get_mut(&info.id).expect("Every ContainerLogInfo has it's ContainerState"). The expect encodes the bookkeeping invariant that every started id has a live entry; it panics when an info message arrives for an id that is not (or no longer) in the map, i.e. the start/finish bookkeeping desynchronized inside Vector.
Source
Thrown at src/sources/docker_logs/mod.rs:597
}
let id = ContainerId::new(id);
self.containers.insert(id.clone(), self.esb.start(id, None));
});
Ok(self)
}
async fn run(mut self) {
loop {
tokio::select! {
value = self.main_recv.recv() => {
match value {
Some(Ok(info)) => {
let state = self
.containers
.get_mut(&info.id)
.expect("Every ContainerLogInfo has it's ContainerState");
if state.return_info(info) {
self.esb.restart(state);
}
},
Some(Err((id,persistence))) => {
let state = self
.containers
.remove(&id)
.expect("Every started ContainerId has it's ContainerState");
match persistence{
ErrorPersistence::Transient => if state.is_running() {
let backoff= Some(self.backoff_duration);
self.containers.insert(id.clone(), self.esb.start(id, backoff));
}
// Forget the container since the error is permanent.
ErrorPersistence::Permanent => (),
}
}View on GitHub (pinned to 3708c39b12)
Solutions
- Capture a repro (Vector version, docker events around the panic, debug logs for docker_logs) and file an upstream issue - this is an internal invariant break
- Move to a Vector release where docker_logs is known stable for your churn pattern
- Patch locally: replace the expect with a match that warns and skips the stale info
- Run the source under a supervisor/restart policy until patched
Example fix
// before
let state = self
.containers
.get_mut(&info.id)
.expect("Every ContainerLogInfo has it's ContainerState");
// after
let Some(state) = self.containers.get_mut(&info.id) else {
warn!(message = "ContainerLogInfo without ContainerState, skipping", id = %info.id);
continue;
}; Defensive patterns
Strategy: validation
Validate before calling
// before accessing the map in the actor loop:
match self.containers.get_mut(&info.id) {
Some(state) => { /* return_info / restart logic */ }
None => {
warn!(message = "stale ContainerLogInfo", id = %info.id);
continue;
}
} Type guard
fn state_of<'a>(
containers: &'a mut HashMap<ContainerId, ContainerState>,
id: &ContainerId,
) -> Option<&'a mut ContainerState> {
containers.get_mut(id)
} Try / catch
// Rust panics in async tasks are not catchable per-message; guard instead:
if let Some(state) = self.containers.get_mut(&info.id) {
if state.return_info(info) {
self.esb.restart(state);
}
} else {
warn!(id = %info.id, "container info without state");
} Prevention
- Model actor maps as fallible lookups; never expect() on message-driven state access
- Log id + current map size when a lookup misses to make desyncs diagnosable
- Add stress tests with rapid container start/stop churn to CI
When it happens
Trigger: A ContainerLogInfo delivered after the error path already removed the state for that id, or a future returning its info twice - both are internal ordering bugs between esb.start/restart, return_info, and the map mutations, not a Docker-side condition.
Common situations: Docker engine restarts and rapid container churn stressing the restart bookkeeping, or regressions after docker_logs refactors in specific Vector versions.
Related errors
- Every started ContainerId has it's ContainerState
- `.` must exist in the event
- `message` must exist in the event
- `docker_logs.stream` must exist in the metadata
- stream must exist in the event
AI-assisted analysis of vectordotdev/vector@3708c39b12 (2026-08-20).
Data as JSON: /api/errors/ac2c6d3cf7baa338.
Report an issue: GitHub.