calesthio/OpenMontage · error · KlingAPIError

Kling Classic result path data.task_result.{result_key} is n

Error message

Kling Classic result path data.task_result.{result_key} is not a list

What it means

KlingAPIError raised inside poll_classic when the task reached the success status but data.task_result.<result_key> is present yet not a list. The poller's contract is to return a list of output records (usually one element with a URL); a dict, string, or null in that position violates the contract, so it refuses to return a malformed result rather than crash later on iteration.

Source

Thrown at tools/_kling/client.py:93

    def poll_classic(
        self,
        path: str,
        task_id: str,
        result_key: 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(f"{path.rstrip('/')}/{task_id}")
            payload = data.get("data") or {}
            status = payload.get("task_status") or payload.get("status")
            if status == CLASSIC_SUCCESS_STATUS:
                task_result = payload.get("task_result") or {}
                outputs = task_result.get(result_key) or []
                if not isinstance(outputs, list):
                    raise KlingAPIError(f"Kling Classic result path data.task_result.{result_key} is not a list")
                return outputs
            if status == CLASSIC_FAILURE_STATUS:
                message = payload.get("task_status_msg") or payload.get("message") or "Kling Classic task failed"
                raise KlingAPIError(str(message), code=payload.get("task_status"), response=data)
            if status not in CLASSIC_PENDING_STATUSES:
                raise KlingAPIError(f"Unexpected Kling Classic task status {status!r}", response=data)
            time.sleep(min(poll_interval, max(0.0, deadline - time.time())))
        raise TimeoutError(f"Kling Classic task {task_id} timed out after {timeout_seconds}s")

    def create_turbo(self, path: str, payload: dict[str, Any]) -> str:
        data = self.post(path, payload)
        task_id = ((data.get("data") or {}).get("id"))
        if not task_id:
            raise KlingAPIError(f"Kling Turbo create response missing data.id: {data}")
        return str(task_id)

    def poll_turbo(
        self,

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. Inspect the raw payload: temporarily log the full data at success status and find the actual key/shape under data.task_result
  2. Correct the result_key argument to the endpoint's documented result field for that route
  3. If the field legitimately became a single object, wrap it client-side: outputs = [obj] if isinstance(obj, dict) else obj
  4. Pin to a known-good provider API version / check the changelog if Kling shipped a breaking change

Example fix

# before
outputs = client.poll_classic(path, task_id, result_key="videos")  # dict comes back

# after
outputs = client.poll_classic(path, task_id, result_key="task_result")
if isinstance(outputs, dict):
    outputs = [outputs]
Defensive patterns

Strategy: type-guard

Validate before calling

# verify result_key against the raw success payload once, then poll
sample = client.get(f"{path.rstrip('/')}/{task_id}")
result = ((sample.get("data") or {}).get("task_result") or {}).get(result_key)
if result is not None and not isinstance(result, list):
    raise SystemExit(f"result_key {result_key!r} is {type(result).__name__}, not list — wrong key for this endpoint?")

Type guard

def is_output_list(value) -> bool:
    return isinstance(value, list)

Try / catch

try:
    outputs = client.poll_classic(path, task_id, result_key)
except KlingAPIError as e:
    if "is not a list" in str(e):
        raise SystemExit(f"result shape mismatch for key {result_key!r} — inspect data.task_result in the raw response")
    raise

Prevention

When it happens

Trigger: Polling a completed classic task where result_key doesn't match this endpoint's actual result field — e.g. asking for 'task_result.images' when the route returns a single object under a different key — so .get(result_key) yields something non-list; also schema changes on Kling's side after success.

Common situations: Calling poll_classic with the result_key copied from a different endpoint's example; API revision changing an endpoint from list-shaped to object-shaped results; task_result partially populated for multi-output tasks.

Related errors


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