{"record":{"id":"02414222d583f131","repo":"calesthio/OpenMontage","slug":"kling-turbo-result-path-data-0-outputs-is-not-a-l","errorCode":null,"errorMessage":"Kling Turbo result path data[0].outputs is not a list","messagePattern":"Kling Turbo result path data\\[0\\]\\.outputs is not a list","errorType":"exception","errorClass":"KlingAPIError","httpStatus":null,"severity":"error","filePath":"tools/_kling/client.py","lineNumber":127,"sourceCode":"\n    def poll_turbo(\n        self,\n        task_id: str,\n        timeout_seconds: int = 900,\n        poll_interval: float = 5.0,\n    ) -> list[dict[str, Any]]:\n        deadline = time.time() + timeout_seconds\n        while time.time() < deadline:\n            data = self.get(\"/tasks\", params={\"task_ids\": task_id})\n            records = data.get(\"data\") or []\n            if not records:\n                raise KlingAPIError(f\"Kling Turbo poll response missing data[0]: {data}\")\n            record = records[0]\n            status = record.get(\"status\") or record.get(\"task_status\")\n            if status == TURBO_SUCCESS_STATUS:\n                outputs = record.get(\"outputs\") or []\n                if not isinstance(outputs, list):\n                    raise KlingAPIError(\"Kling Turbo result path data[0].outputs is not a list\")\n                return outputs\n            if status == TURBO_FAILURE_STATUS:\n                message = record.get(\"message\") or record.get(\"error\") or \"Kling Turbo task failed\"\n                raise KlingAPIError(str(message), code=record.get(\"code\"), request_id=record.get(\"request_id\"), response=data)\n            if status not in TURBO_PENDING_STATUSES:\n                raise KlingAPIError(f\"Unexpected Kling Turbo task status {status!r}\", response=data)\n            time.sleep(min(poll_interval, max(0.0, deadline - time.time())))\n        raise TimeoutError(f\"Kling Turbo task {task_id} timed out after {timeout_seconds}s\")\n\n    def _request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:\n        url = self._url(path)\n        last_error: KlingAPIError | None = None\n        for attempt in range(self.max_retries + 1):\n            try:\n                response = getattr(self.session, method)(url, headers=self.headers, timeout=30, **kwargs)\n                self._raise_for_http_error(response)\n                data = response.json()\n                self._raise_for_business_error(data)","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/tools/_kling/client.py#L109-L145","documentation":"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.","triggerScenarios":"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.","commonSituations":"Upstream Turbo API version bump changing the result contract; a custom gateway aggregator (fal/HeyGen-style) normalizing outputs differently than the raw Kling shape.","solutions":["Log the full record (in error.response) to see the actual type of outputs","Update poll_turbo() to handle the new shape (e.g. dict of lists, or string URL)","Pin the gateway/API version if the Turbo deployment offers versioned paths","Report/confirm the contract change against current Turbo API docs"],"exampleFix":"// before\noutputs = record.get('outputs') or []\nif not isinstance(outputs, list):\n    raise KlingAPIError('Kling Turbo result path data[0].outputs is not a list')\n\n// after (handle dict-of-lists shape)\noutputs = record.get('outputs') or []\nif isinstance(outputs, dict):\n    outputs = [item for items in outputs.values() if isinstance(items, list) for item in items]\nelif not isinstance(outputs, list):\n    raise KlingAPIError(f'unexpected outputs shape: {type(outputs).__name__}')","handlingStrategy":"type-guard","validationCode":"def outputs_is_list(record: dict) -> bool:\n    return isinstance(record.get('outputs'), list)","typeGuard":"def extract_turbo_outputs(record: dict) -> list:\n    outputs = record.get('outputs')\n    if isinstance(outputs, list):\n        return outputs\n    if isinstance(outputs, dict):  # shape drift: dict of lists\n        return [i for v in outputs.values() if isinstance(v, list) for i in v]\n    raise KlingAPIError(f'unexpected outputs type: {type(outputs).__name__}')","tryCatchPattern":"try:\n    outputs = client.poll_turbo(task_id)\nexcept KlingAPIError as e:\n    if 'outputs is not a list' in str(e):\n        logger.error('schema drift, record: %s', e.response)\n    raise","preventionTips":["Contract-test poll parsing against a recorded success payload in CI","Log the raw record whenever the outputs shape is unexpected","Pin gateway/API versions where available"],"tags":["kling","api","turbo","schema-drift","response-shape"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}