calesthio/OpenMontage · error · KlingAPIError

Unexpected Kling Turbo task status {status!r}

Error message

Unexpected Kling Turbo task status {status!r}

What it means

Raised when poll_turbo() sees a status that is neither TURBO_SUCCESS_STATUS, TURBO_FAILURE_STATUS, nor in TURBO_PENDING_STATUSES. Like the Classic variant, this guards against unknown status enum values from schema drift or unusual task lifecycle states, and carries the full response payload on the exception.

Source

Thrown at tools/_kling/client.py:133

    ) -> 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)
                return data
            except KlingAPIError as error:
                last_error = error
                if attempt >= self.max_retries or not is_retryable_kling_error(error):
                    raise
                time.sleep(min(2.0 * (attempt + 1), 8.0))

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. Log error.response to capture the exact status string and record shape
  2. Compare against current Turbo API docs and update TURBO_PENDING_STATUSES or add terminal-state handling in schemas.py
  3. If the status is terminal (cancelled/expired), surface it as a failure instead of polling forever

Example fix

// schemas.py — before
TURBO_PENDING_STATUSES = {'pending', 'running'}

// schemas.py — after (extend once the new status is confirmed non-terminal)
TURBO_PENDING_STATUSES = {'pending', 'running', 'queued'}
Defensive patterns

Strategy: try-catch

Validate before calling

from tools._kling.schemas import TURBO_PENDING_STATUSES, TURBO_SUCCESS_STATUS, TURBO_FAILURE_STATUS

def classify_turbo_status(record: dict) -> str:
    s = record.get('status') or record.get('task_status')
    if s == TURBO_SUCCESS_STATUS: return 'success'
    if s == TURBO_FAILURE_STATUS: return 'failure'
    if s in TURBO_PENDING_STATUSES: return 'pending'
    return 'unknown'

Type guard

def turbo_status_is_handled(status: str) -> bool:
    from tools._kling.schemas import TURBO_PENDING_STATUSES, TURBO_SUCCESS_STATUS, TURBO_FAILURE_STATUS
    return status in (TURBO_PENDING_STATUSES | {TURBO_SUCCESS_STATUS, TURBO_FAILURE_STATUS})

Try / catch

try:
    outputs = client.poll_turbo(task_id)
except KlingAPIError as e:
    if 'Unexpected Kling Turbo task status' in str(e):
        logger.error('unknown turbo status, response: %s', e.response)
    raise

Prevention

When it happens

Trigger: The Turbo gateway introduces a new status (e.g. 'cancelled', 'expired', 'partial'); the task was cancelled via the console; record.get('status') is empty while task_status holds a value outside the known sets.

Common situations: Turbo API version updates adding lifecycle states; tasks cancelled or expiring between polls; aggregator gateways translating statuses into non-standard strings.

Related errors


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