iflytek/astron-agent · error · GateError

Docker Compose config did not return valid JSON

Error message

Docker Compose config did not return valid JSON

What it means

_render_compose runs the selected Compose command with 'config --format json' and parses stdout with json.loads. If the output is not valid JSON (JSONDecodeError or TypeError, e.g. non-string stdout), it wraps the failure in GateError('Docker Compose config did not return valid JSON'). This guards against Compose implementations that ignore the --format json flag and emit YAML or human-readable text.

Solutions

  1. Upgrade to Docker Compose v2 so 'config --format json' emits pure JSON on stdout.
  2. Run the command manually and inspect stdout for non-JSON prefixes (warnings, banners) and remove their source.
  3. Check for shell aliases/wrappers around docker compose in the execution environment (unalias, use absolute path).
  4. Capture stdout separately from stderr (2>/dev/null) when testing to confirm which stream carries the noise.

Example fix

// diagnostic before fix
docker compose config --format json | head -c 200   # shows YAML or warning text
// after upgrade
docker compose version  # v2.x
docker compose config --format json | python -m json.tool > /dev/null  # ok
Defensive patterns

Strategy: validation

Validate before calling

import json, subprocess
out = subprocess.run(["docker","compose","-f",f,"config","--format","json"], capture_output=True, text=True).stdout
json.loads(out)  # raises early if not JSON

Type guard

def is_json_object(s: str) -> bool:
    import json
    try:
        return isinstance(json.loads(s), dict)
    except (json.JSONDecodeError, TypeError):
        return False

Try / catch

try:
    cfg = render_compose(f)
except GateError as e:
    if "valid JSON" in str(e):
        log(compose_raw_stdout)  # inspect non-JSON noise
    raise

Prevention

When it happens

Trigger: The chosen compose implementation exits 0 but prints YAML/human text instead of JSON for 'config --format json' (older/v1 or aliased implementations); stdout polluted by warnings/pre-banner text; or the command output is None.

Common situations: Shell alias or wrapper intercepting docker-compose; compose v1 accepting --format but rendering text; plugin printing deprecation warnings on stdout; CI shim that logs before the command output.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/6c9d527507d6f62b. Report an issue: GitHub.

Appendix: source

Thrown at docker/astronAgent/scripts/verify_security_contract.py:184

        "'config --format json' is required"
    )


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)