{"record":{"id":"3961f1304aef442a","repo":"PaddlePaddle/PaddleOCR","slug":"status-progress-must-be-an-object","errorCode":null,"errorMessage":"Status progress must be an object.","messagePattern":"Status progress must be an object\\.","errorType":"exception","errorClass":"ResponseFormatError","httpStatus":null,"severity":"error","filePath":"api_sdk/typescript/src/internal/poller.ts","lineNumber":133,"sourceCode":"  }\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)) {\n    throw new ResponseFormatError(\"Status progress must be an object.\");\n  }\n  return {\n    totalPages: typeof progress.totalPages === \"number\" ? progress.totalPages : 0,\n    extractedPages: typeof progress.extractedPages === \"number\" ? progress.extractedPages : 0,\n    startTime: typeof progress.startTime === \"string\" ? progress.startTime : undefined,\n    endTime: typeof progress.endTime === \"string\" ? progress.endTime : undefined,\n  };\n}\n\nfunction resultJsonUrl(data: unknown): string {\n  if (!isRecord(data) || !isRecord(data.resultUrl) || typeof data.resultUrl.jsonUrl !== \"string\") {\n    throw new ResponseFormatError(\"Done job response is missing resultUrl.jsonUrl.\");\n  }\n  return data.resultUrl.jsonUrl;\n}\n\nfunction stringMap(value: Record<string, unknown>): Record<string, string> {\n  const result: Record<string, string> = {};","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/api_sdk/typescript/src/internal/poller.ts#L115-L151","documentation":"Thrown by normalizeProgress() when a job status response includes an extractProgress field whose value is neither absent/null nor a plain object. The poller tolerates missing progress entirely, but if progress data is present it must be an object with numeric totalPages/extractedPages fields. Any other JSON type (number, string, array, boolean) triggers this ResponseFormatError.","triggerScenarios":"Polling a job whose status response contains \"extractProgress\": 42, \"extractProgress\": \"50%\", or \"extractProgress\": [1,2] instead of an object like {\"totalPages\":10,\"extractedPages\":5}. Happens when the server changes progress serialization (e.g. flattening to a percentage number).","commonSituations":"Server refactor that switched progress from object to scalar percent; partial serialization bugs where progress is emitted as a JSON string; SDK version older than a server progress format change.","solutions":["Capture the raw status response and inspect the JSON type of extractProgress","Fix the server to emit progress as an object { totalPages, extractedPages } or omit it entirely","Upgrade/align SDK and server versions so both expect the object shape","If the field is genuinely unused, remove it from the response"],"exampleFix":"// before (server response)\n{ \"state\": \"running\", \"extractProgress\": 55 }\n\n// after\n{ \"state\": \"running\", \"extractProgress\": { \"totalPages\": 100, \"extractedPages\": 55 } }","handlingStrategy":"type-guard","validationCode":"const raw = await client.getJobStatus(jobId);\nif (raw.extractProgress != null && typeof raw.extractProgress !== \"object\") {\n  // server emits non-object progress; strip it before handing the payload to the poller\n  delete raw.extractProgress;\n}","typeGuard":"function isProgressObject(v: unknown): v is { totalPages?: number; extractedPages?: number } {\n  return v == null || (typeof v === \"object\" && !Array.isArray(v));\n}","tryCatchPattern":"try { await poller.wait(jobId); } catch (e) {\n  if (e instanceof ResponseFormatError && /progress must be an object/.test(e.message)) {\n    // server-side serialization bug: report to API owner, fall back to non-progress polling\n  } else throw e;\n}","preventionTips":["Contract-test the status endpoint shape (extractProgress is object-or-absent) in CI","Keep SDK and server versions locked together in deployments","When changing server progress format, gate it on minimum client version"],"tags":["api-sdk","typescript","polling","contract-mismatch","serialization"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}