jdx/mise · error · eyre::Report

container inspection returned an unexpected number of rows

Error message

container inspection returned an unexpected number of rows

What it means

To compare deployed config hashes, mise lists the project's containers, then runs `docker inspect --format '{{json .Config.Labels}}'` over their IDs and expects exactly one JSON line per container. A row-count mismatch means the container set changed between the two engine calls (containers created, removed, or restarting), so label extraction is abandoned rather than misattributing hashes.

Source

Thrown at src/system/compose.rs:554

    fn add_container_hashes(&self, containers: &mut [ComposeContainer]) -> Result<()> {
        if containers.is_empty() {
            return Ok(());
        }
        let ids = containers
            .iter()
            .map(|container| container.id.clone())
            .collect::<Vec<_>>();
        let mut args = vec![
            "inspect".to_string(),
            "--format".to_string(),
            "{{json .Config.Labels}}".to_string(),
        ];
        args.extend(ids);
        let output = self.engine_output(&args)?;
        let lines = output.lines().collect::<Vec<_>>();
        if lines.len() != containers.len() {
            bail!("container inspection returned an unexpected number of rows");
        }
        for (container, line) in containers.iter_mut().zip(lines) {
            let labels: HashMap<String, String> = serde_json::from_str(line)?;
            container.config_hash = labels.get("com.docker.compose.config-hash").cloned();
        }
        Ok(())
    }

    fn desired(&self) -> String {
        match self.state {
            ComposeState::Running => format!(
                "running{}; pull {:?}; build {:?}; recreate {:?}; wait {}",
                selected_suffix(&self.services),
                self.pull,
                self.build,
                self.recreate,
                self.wait
            )

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Re-run `mise bootstrap compose apply` — the mismatch is transient and the next classification pass usually succeeds.
  2. Stabilize the project first: fix crash-looping services or pause competing automation (watchtower/CI) during converge.
  3. If it persists, capture `docker compose -p <project> ps -a` twice in a row and diff to confirm what is churning.
Defensive patterns

Strategy: retry

Try / catch

for attempt in 0..3 {
    match converge_project(&request) {
        Ok(()) => break,
        Err(e) if e.to_string().contains("unexpected number of rows") && attempt < 2 => {
            std::thread::sleep(std::time::Duration::from_millis(500)); // container set churned; re-inspect
        }
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: Containers appear or vanish between mise's ps/inspect calls: a `restart: always` crash loop churning containers, another operator or CI job concurrently running compose on the same project, or oneshot containers exiting in that window.

Common situations: Unstable services during converge (failing healthchecks with auto-restart); overlapping automation (watchtower, CI deploy, a teammate) touching the same compose project; heavily loaded engines with slow inspect responses.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/1d93fa1ac8fcd854. Report an issue: GitHub.