{"record":{"id":"db346a4e018da7fb","repo":"iflytek/astron-agent","slug":"docker-compose-config-returned-an-unexpected-json-root","errorCode":null,"errorMessage":"Docker Compose config returned an unexpected JSON root","messagePattern":"Docker Compose config returned an unexpected JSON root","errorType":"console","errorClass":"GateError","httpStatus":null,"severity":"error","filePath":"docker/astronAgent/scripts/verify_security_contract.py","lineNumber":186,"sourceCode":"\n\ndef _render_compose(compose_file: Path) -> Mapping[str, Any]:\n    base_command = _select_compose_command(compose_file)\n    completed = subprocess.run(\n        list(base_command) + [\"-f\", compose_file.name, \"config\", \"--format\", \"json\"],\n        cwd=str(compose_file.parent),\n        stdout=subprocess.PIPE,\n        stderr=subprocess.PIPE,\n        universal_newlines=True,\n        check=False,\n    )\n\n    try:\n        rendered = json.loads(completed.stdout)\n    except (json.JSONDecodeError, TypeError) as exc:\n        raise GateError(\"Docker Compose config did not return valid JSON\") from exc\n    if not isinstance(rendered, dict):\n        raise GateError(\"Docker Compose config returned an unexpected JSON root\")\n    return rendered\n\n\ndef _mapping(value: Any) -> Mapping[str, Any]:\n    return value if isinstance(value, dict) else {}\n\n\ndef _mounts(service: Mapping[str, Any], target: str) -> List[Mapping[str, Any]]:\n    volumes = service.get(\"volumes\", [])\n    if not isinstance(volumes, list):\n        return []\n    return [\n        volume\n        for volume in volumes\n        if isinstance(volume, dict) and volume.get(\"target\") == target\n    ]\n\n","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/docker/astronAgent/scripts/verify_security_contract.py#L168-L204","documentation":"After successfully parsing Compose config output as JSON, _render_compose checks the root value is a dict (Mapping) because the script expects the standard Compose config document shape {services: ...}. If json.loads yields a list, string, number or null, it raises GateError('Docker Compose config returned an unexpected JSON root').","triggerScenarios":"'compose config --format json' returns valid JSON whose top-level value is not an object — e.g. a nonstandard implementation, a wrapper emitting an array of documents, or intercepted output replaced with something else.","commonSituations":"Custom compose wrappers/shims that post-process output; hypothetically patched or bleeding-edge Compose builds changing output shape; a test double/mocked compose script returning the wrong structure.","solutions":["Verify the real Compose v2 binary is invoked (docker compose version; avoid shims/wrappers) since it always emits an object root for config --format json.","Remove any wrapper scripts or output post-processing around the compose command in PATH.","Inspect the raw output: echo the stdout to a file and check its root type with python -c \"import json,sys; print(type(json.load(open('out.json'))))\".","If a wrapper is required, make it pass through the object unchanged."],"exampleFix":"// before (wrapper emits list)\n[{\"service\":\"x\"}]  # GateError: unexpected JSON root\n// after (pass-through)\nexec /usr/libexec/docker/cli-plugins/docker-compose \"$@\"","handlingStrategy":"type-guard","validationCode":"import json, subprocess\nout = subprocess.run([\"docker\",\"compose\",\"-f\",f,\"config\",\"--format\",\"json\"], capture_output=True, text=True).stdout\nassert isinstance(json.loads(out), dict)","typeGuard":"def is_mapping_root(raw: str) -> bool:\n    import json\n    try:\n        return isinstance(json.loads(raw), dict)\n    except (json.JSONDecodeError, TypeError):\n        return False","tryCatchPattern":"try:\n    cfg = render_compose(f)\nexcept GateError:\n    cfg = None\nif cfg is None:\n    fail_with_diagnostics()","preventionTips":["Do not post-process or transform compose output in wrappers.","Pin official Compose v2 binaries."],"tags":["docker-compose","json","schema"],"backgroundTag":"unexpected-response-shape","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}