{"record":{"id":"055d52239a68eedf","repo":"PaddlePaddle/PaddleOCR","slug":"status-response-is-missing-state","errorCode":null,"errorMessage":"Status response is missing state.","messagePattern":"Status response is missing state\\.","errorType":"exception","errorClass":"ResponseFormatError","httpStatus":null,"severity":"error","filePath":"api_sdk/typescript/src/internal/poller.ts","lineNumber":114,"sourceCode":"\n  private async withPollTimeout<T>(jobId: string, remainingMs: number, operation: () => Promise<T>): Promise<T> {\n    if (remainingMs <= 0) {\n      throw new PollTimeoutError(jobId, this.maxWaitTime);\n    }\n    try {\n      return await operation();\n    } catch (error) {\n      if (error instanceof RequestTimeoutError && error.timeoutMs === remainingMs) {\n        throw new PollTimeoutError(jobId, this.maxWaitTime, { cause: error });\n      }\n      throw error;\n    }\n  }\n}\n\nfunction normalizeStatus(jobId: string, data: unknown): JobStatus {\n  if (!isRecord(data) || typeof data.state !== \"string\") {\n    throw new ResponseFormatError(\"Status response is missing state.\");\n  }\n  if (![\"pending\", \"running\", \"done\", \"failed\"].includes(data.state)) {\n    throw new ResponseFormatError(`Unknown job state: ${data.state}`);\n  }\n  return {\n    jobId,\n    state: data.state as JobStatus[\"state\"],\n    progress: normalizeProgress(data.extractProgress),\n    resultUrl: isRecord(data.resultUrl) ? stringMap(data.resultUrl) : undefined,\n    errorMsg: typeof data.errorMsg === \"string\" ? data.errorMsg : undefined,\n  };\n}\n\nfunction normalizeProgress(progress: unknown): Progress | undefined {\n  if (progress === undefined || progress === null) {\n    return undefined;\n  }\n  if (!isRecord(progress)) {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/api_sdk/typescript/src/internal/poller.ts#L96-L132","documentation":"ResponseFormatError with 'Status response is missing state.' is thrown by normalizeStatus() when a job status payload's data object is not a record containing a string `state` field. Every getJobStatus/getStatus and every batch extractResult entry must carry a state string ('pending' | 'running' | 'done' | 'failed'); without it the SDK cannot classify the job.","triggerScenarios":"Polling getStatus(jobId) or the internal getJobStatus where the server returns a data object without state — e.g. the jobId is unknown and the server answers 200 with an error-shaped body, a field rename in a newer API, or batch entries for jobs not yet registered.","commonSituations":"Passing an invalid or expired jobId; SDK version behind a server-side contract change (state renamed to status); mocks returning partial objects; middleware returning { data: { msg: ... } } on soft errors.","solutions":["Verify the jobId is the exact non-empty string returned by submit (see error 32) and has not expired","Capture the raw status response with a custom fetchImpl to inspect the data object","Upgrade the SDK to match the deployed API version — state is part of the status contract","Fix stubs to include state: 'pending' in every status payload"],"exampleFix":"try {\n  const status = await poller.getStatus(jobId);\n} catch (e) {\n  if (e instanceof ResponseFormatError && e.message.includes(\"state\")) {\n    logger.error(\"Status payload unusable for job\", jobId);\n    // do not loop: contract is broken or the id is invalid\n  }\n}","handlingStrategy":"type-guard","validationCode":"const VALID_STATES = [\"pending\", \"running\", \"done\", \"failed\"] as const;\nfunction isValidStatePayload(data: unknown): boolean {\n  return typeof data === \"object\" && data !== null\n    && typeof (data as any).state === \"string\"\n    && (VALID_STATES as readonly string[]).includes((data as any).state);\n}","typeGuard":"function isJobStatusPayload(v: unknown): v is { state: string } {\n  return typeof v === \"object\" && v !== null && typeof (v as { state?: unknown }).state === \"string\";\n}","tryCatchPattern":"try {\n  const status = await poller.getStatus(jobId);\n} catch (e) {\n  if (e instanceof ResponseFormatError && e.message.includes(\"state\")) {\n    // payload unusable: stop polling this id and surface the contract break\n    throw new Error(`Status payload for ${jobId} has no state — wrong id or API drift`);\n  }\n  throw e;\n}","preventionTips":["Store jobIds as opaque strings exactly as returned; never reconstruct or truncate them","Add a status-payload contract test (state field present and one of the four values) to CI","Upgrade SDK and server together; the state field name is part of the contract"],"tags":["response-format","status","contract","typescript"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}