{"record":{"id":"6c7cf1d72947ddf0","repo":"windmill-labs/windmill","slug":"expected-an-object-map-for-the-ansible-galaxy-c","errorCode":null,"errorMessage":"Expected an object (map) for the `ansible-galaxy collection list` command output and got {}","messagePattern":"Expected an object \\(map\\) for the `ansible-galaxy collection list` command output and got (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/windmill-worker/src/ansible_executor.rs","lineNumber":1097,"sourceCode":"    let mut ret = HashMap::new();\n    let mut logs = String::new();\n\n    if !output.status.success() {\n        let stderr = String::from_utf8(output.stderr)?;\n        return Err(anyhow!(\n            \"Error getting ansible collection versions: {stderr}\"\n        ));\n    }\n\n    let stdout = String::from_utf8(output.stdout)?;\n\n    let val: serde_json::Value = serde_json::from_str(&stdout)?;\n\n    let Some(own_collections) = val.get(format!(\"{}/ansible_collections\", job_dir)) else {\n        return Ok((ret, logs));\n    };\n\n    let collections = own_collections.as_object().ok_or(anyhow!(\n        \"Expected an object (map) for the `ansible-galaxy collection list` command output and got {}\",\n        own_collections\n    ))?;\n\n    for (c_name, c) in collections.iter() {\n        if let Some(v) = c.get(\"version\").and_then(|v| v.as_str()) {\n            // TODO: Check if version is not something like `(undefined)`\n            ret.insert(c_name.clone(), v.to_string());\n        } else {\n            logs.push_str(&format!(\"Failed to get version for collection `{}`. Expected an object with a string in the `version` field but received {}\\n\", c_name, c));\n        }\n    }\n\n    Ok((ret, logs))\n}\n\npub async fn get_role_locks(job_dir: &str) -> anyhow::Result<(HashMap<String, String>, String)> {\n    let mut ansible_cmd = Command::new(ANSIBLE_GALAXY_PATH.as_str());","sourceCodeStart":1079,"sourceCodeEnd":1115,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/backend/windmill-worker/src/ansible_executor.rs#L1079-L1115","documentation":"After `ansible-galaxy collection list` succeeds, its stdout is parsed as JSON and Windmill looks up the `<job_dir>/ansible_collections` key. If that key exists but is not a JSON object (map), this error is thrown because the per-collection data cannot be iterated as name→info entries.","triggerScenarios":"ansible-galaxy emits a `--format json` output whose `<job_dir>/ansible_collections` entry is a non-object value (array, string, null) — e.g. an unexpected CLI output format from a different ansible-core version, or the path key colliding with something odd in the environment.","commonSituations":"ansible-core version whose `collection list --format json` shape differs from what the worker expects; custom COLLECTIONS_PATHS producing odd output; piping/truncating stdout so the JSON is reshaped.","solutions":["Check the exact rendered value in the message and compare with `ansible-galaxy collection list --format json` output on the worker.","Upgrade/downgrade ansible-core to a version whose JSON output has a per-path object mapping collection names to objects.","Ensure the job directory is passed as an absolute path so the expected `<job_dir>/ansible_collections` key matches.","Report/patch the parser if a new ansible-core release changed the output contract."],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// sanity-check the shape the worker expects:\nout=$(ansible-galaxy collection list --format json)\necho \"$out\" | jq -e --arg p \"$JOB_DIR/ansible_collections\" '.[$p] | type == \"object\"' || echo 'unexpected shape for expected key'","typeGuard":"fn is_collection_map(v: &serde_json::Value) -> bool { v.as_object().map(|m| m.values().all(|c| c.is_object())).unwrap_or(false) }","tryCatchPattern":"match serde_json::from_str::<serde_json::Value>(&stdout) {\n    Ok(v) => /* proceed, but verify val[key].as_object() before iterating */,\n    Err(e) => log::error!(\"ansible-galaxy JSON unparseable: {e}\"),\n}","preventionTips":["Pin ansible-core so `collection list --format json` output shape is stable","Verify JSON output manually after any ansible upgrade","Use absolute job_dir paths so the expected key matches"],"tags":["ansible","json","schema-mismatch"],"backgroundTag":"unexpected-json-shape","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}