iflytek/astron-agent · error · GateError
Docker Compose config returned an unexpected JSON root
Error message
Docker Compose config returned an unexpected JSON root
What it means
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').
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.
Example fix
// before (wrapper emits list)
[{"service":"x"}] # GateError: unexpected JSON root
// after (pass-through)
exec /usr/libexec/docker/cli-plugins/docker-compose "$@" Defensive patterns
Strategy: type-guard
Validate before calling
import json, subprocess out = subprocess.run(["docker","compose","-f",f,"config","--format","json"], capture_output=True, text=True).stdout assert isinstance(json.loads(out), dict)
Type guard
def is_mapping_root(raw: str) -> bool:
import json
try:
return isinstance(json.loads(raw), dict)
except (json.JSONDecodeError, TypeError):
return False Try / catch
try:
cfg = render_compose(f)
except GateError:
cfg = None
if cfg is None:
fail_with_diagnostics() Prevention
- Do not post-process or transform compose output in wrappers.
- Pin official Compose v2 binaries.
When it happens
Trigger: '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.
Common situations: 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.
Related errors
- Docker Compose config did not return valid JSON
- jsonData.message
- 8118
- Header mismatch! Expected headers: , Actual headers:
- 8517
AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12).
Data as JSON: /api/errors/db346a4e018da7fb.
Report an issue: GitHub.
Appendix: source
Thrown at docker/astronAgent/scripts/verify_security_contract.py:186
def _render_compose(compose_file: Path) -> Mapping[str, Any]:
base_command = _select_compose_command(compose_file)
completed = subprocess.run(
list(base_command) + ["-f", compose_file.name, "config", "--format", "json"],
cwd=str(compose_file.parent),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
check=False,
)
try:
rendered = json.loads(completed.stdout)
except (json.JSONDecodeError, TypeError) as exc:
raise GateError("Docker Compose config did not return valid JSON") from exc
if not isinstance(rendered, dict):
raise GateError("Docker Compose config returned an unexpected JSON root")
return rendered
def _mapping(value: Any) -> Mapping[str, Any]:
return value if isinstance(value, dict) else {}
def _mounts(service: Mapping[str, Any], target: str) -> List[Mapping[str, Any]]:
volumes = service.get("volumes", [])
if not isinstance(volumes, list):
return []
return [
volume
for volume in volumes
if isinstance(volume, dict) and volume.get("target") == target
]
View on GitHub (pinned to 5e758547a8)