calesthio/OpenMontage · error · KlingAPIError

Kling Turbo result path data[0].outputs is not a list

Error message

Kling Turbo result path data[0].outputs is not a list

What it means

Defensive type check in poll_turbo(): the task reached TURBO_SUCCESS_STATUS but record['outputs'] is not a list (it is None-coerced to [] only if falsy; a non-empty dict or string triggers the error). This indicates the Turbo gateway's success payload no longer matches the expected data[0].outputs array shape.

Source

Thrown at tools/_kling/client.py:127

    def poll_turbo(
        self,
        task_id: str,
        timeout_seconds: int = 900,
        poll_interval: float = 5.0,
    ) -> list[dict[str, Any]]:
        deadline = time.time() + timeout_seconds
        while time.time() < deadline:
            data = self.get("/tasks", params={"task_ids": task_id})
            records = data.get("data") or []
            if not records:
                raise KlingAPIError(f"Kling Turbo poll response missing data[0]: {data}")
            record = records[0]
            status = record.get("status") or record.get("task_status")
            if status == TURBO_SUCCESS_STATUS:
                outputs = record.get("outputs") or []
                if not isinstance(outputs, list):
                    raise KlingAPIError("Kling Turbo result path data[0].outputs is not a list")
                return outputs
            if status == TURBO_FAILURE_STATUS:
                message = record.get("message") or record.get("error") or "Kling Turbo task failed"
                raise KlingAPIError(str(message), code=record.get("code"), request_id=record.get("request_id"), response=data)
            if status not in TURBO_PENDING_STATUSES:
                raise KlingAPIError(f"Unexpected Kling Turbo task status {status!r}", response=data)
            time.sleep(min(poll_interval, max(0.0, deadline - time.time())))
        raise TimeoutError(f"Kling Turbo task {task_id} timed out after {timeout_seconds}s")

    def _request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
        url = self._url(path)
        last_error: KlingAPIError | None = None
        for attempt in range(self.max_retries + 1):
            try:
                response = getattr(self.session, method)(url, headers=self.headers, timeout=30, **kwargs)
                self._raise_for_http_error(response)
                data = response.json()
                self._raise_for_business_error(data)

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. Log the full record (in error.response) to see the actual type of outputs
  2. Update poll_turbo() to handle the new shape (e.g. dict of lists, or string URL)
  3. Pin the gateway/API version if the Turbo deployment offers versioned paths
  4. Report/confirm the contract change against current Turbo API docs

Example fix

// before
outputs = record.get('outputs') or []
if not isinstance(outputs, list):
    raise KlingAPIError('Kling Turbo result path data[0].outputs is not a list')

// after (handle dict-of-lists shape)
outputs = record.get('outputs') or []
if isinstance(outputs, dict):
    outputs = [item for items in outputs.values() if isinstance(items, list) for item in items]
elif not isinstance(outputs, list):
    raise KlingAPIError(f'unexpected outputs shape: {type(outputs).__name__}')
Defensive patterns

Strategy: type-guard

Validate before calling

def outputs_is_list(record: dict) -> bool:
    return isinstance(record.get('outputs'), list)

Type guard

def extract_turbo_outputs(record: dict) -> list:
    outputs = record.get('outputs')
    if isinstance(outputs, list):
        return outputs
    if isinstance(outputs, dict):  # shape drift: dict of lists
        return [i for v in outputs.values() if isinstance(v, list) for i in v]
    raise KlingAPIError(f'unexpected outputs type: {type(outputs).__name__}')

Try / catch

try:
    outputs = client.poll_turbo(task_id)
except KlingAPIError as e:
    if 'outputs is not a list' in str(e):
        logger.error('schema drift, record: %s', e.response)
    raise

Prevention

When it happens

Trigger: The Turbo API changes its success schema, e.g. outputs becomes an object keyed by content type ({'videos': [...], 'images': [...]}) or a URL string; a proxy wraps outputs in an extra envelope; a partial/A-B response shape reaches the client.

Common situations: Upstream Turbo API version bump changing the result contract; a custom gateway aggregator (fal/HeyGen-style) normalizing outputs differently than the raw Kling shape.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/02414222d583f131. Report an issue: GitHub.