{"record":{"id":"1d93fa1ac8fcd854","repo":"jdx/mise","slug":"container-inspection-returned-an-unexpected-number","errorCode":null,"errorMessage":"container inspection returned an unexpected number of rows","messagePattern":"container inspection returned an unexpected number of rows","errorType":"exception","errorClass":"eyre::Report","httpStatus":null,"severity":"error","filePath":"src/system/compose.rs","lineNumber":554,"sourceCode":"\n    fn add_container_hashes(&self, containers: &mut [ComposeContainer]) -> Result<()> {\n        if containers.is_empty() {\n            return Ok(());\n        }\n        let ids = containers\n            .iter()\n            .map(|container| container.id.clone())\n            .collect::<Vec<_>>();\n        let mut args = vec![\n            \"inspect\".to_string(),\n            \"--format\".to_string(),\n            \"{{json .Config.Labels}}\".to_string(),\n        ];\n        args.extend(ids);\n        let output = self.engine_output(&args)?;\n        let lines = output.lines().collect::<Vec<_>>();\n        if lines.len() != containers.len() {\n            bail!(\"container inspection returned an unexpected number of rows\");\n        }\n        for (container, line) in containers.iter_mut().zip(lines) {\n            let labels: HashMap<String, String> = serde_json::from_str(line)?;\n            container.config_hash = labels.get(\"com.docker.compose.config-hash\").cloned();\n        }\n        Ok(())\n    }\n\n    fn desired(&self) -> String {\n        match self.state {\n            ComposeState::Running => format!(\n                \"running{}; pull {:?}; build {:?}; recreate {:?}; wait {}\",\n                selected_suffix(&self.services),\n                self.pull,\n                self.build,\n                self.recreate,\n                self.wait\n            )","sourceCodeStart":536,"sourceCodeEnd":572,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/system/compose.rs#L536-L572","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run `mise bootstrap compose apply` — the mismatch is transient and the next classification pass usually succeeds.","Stabilize the project first: fix crash-looping services or pause competing automation (watchtower/CI) during converge.","If it persists, capture `docker compose -p <project> ps -a` twice in a row and diff to confirm what is churning."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for attempt in 0..3 {\n    match converge_project(&request) {\n        Ok(()) => break,\n        Err(e) if e.to_string().contains(\"unexpected number of rows\") && attempt < 2 => {\n            std::thread::sleep(std::time::Duration::from_millis(500)); // container set churned; re-inspect\n        }\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["Stop crash-looping services and competing automation (watchtower, CI deploys) before converging.","Treat a one-off mismatch as transient: rerun `mise bootstrap compose apply` before debugging."],"tags":["mise","bootstrap","compose","docker","inspect","race-condition","transient"],"backgroundTag":"docker-inspection-race-condition","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}