{"record":{"id":"9fc7cb4bd3176e3f","repo":"PaddlePaddle/PaddleOCR","slug":"batch-extractresult-item-is-missing-jobid","errorCode":null,"errorMessage":"Batch extractResult item is missing jobId.","messagePattern":"Batch extractResult item is missing jobId\\.","errorType":"exception","errorClass":"ResponseFormatError","httpStatus":null,"severity":"error","filePath":"api_sdk/typescript/src/internal/poller.ts","lineNumber":75,"sourceCode":"    }\n\n    throw new PollTimeoutError(jobId, this.maxWaitTime);\n  }\n\n  async getStatus(jobId: string, signal?: AbortSignal): Promise<JobStatus> {\n    return normalizeStatus(jobId, await this.http.getJobStatus(jobId, signal));\n  }\n\n  async getBatchStatus(batchId: string, signal?: AbortSignal): Promise<BatchStatus> {\n    const data = await this.http.getBatchStatus(batchId, signal);\n    if (!isRecord(data) || !Array.isArray(data.extractResult)) {\n      throw new ResponseFormatError(\"Batch response is missing extractResult.\");\n    }\n    return {\n      batchId,\n      jobs: data.extractResult.map((item) => {\n        if (!isRecord(item) || typeof item.jobId !== \"string\") {\n          throw new ResponseFormatError(\"Batch extractResult item is missing jobId.\");\n        }\n        return normalizeStatus(item.jobId, item);\n      }),\n    };\n  }\n\n  private sleep(ms: number, signal?: AbortSignal): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const timer = setTimeout(resolve, ms);\n      if (!signal) return;\n      signal.addEventListener(\n        \"abort\",\n        () => {\n          clearTimeout(timer);\n          reject(userAbortReason(signal));\n        },\n        { once: true },\n      );","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/api_sdk/typescript/src/internal/poller.ts#L57-L93","documentation":"ResponseFormatError with 'Batch extractResult item is missing jobId.' is thrown while mapping a batch status response when an element of data.extractResult is not an object with a string jobId. Every per-job entry in a batch must carry a jobId so the SDK can normalize it into a JobStatus; one malformed element fails the whole getBatchStatus() call.","triggerScenarios":"getBatchStatus(batchId) where the server's extractResult array contains null entries, entries keyed differently (id/job_id), or inline error objects for jobs that failed before assignment — a single bad element aborts mapping of the entire batch.","commonSituations":"Backend version drift renaming per-job fields; server emitting placeholder entries for rejected submissions; partially-implemented mocks in tests; corrupt batch state after an incident.","solutions":["Capture the raw extractResult via a custom fetchImpl and identify which element lacks jobId","Upgrade the SDK / confirm server version — per-job field naming is part of the contract","If the batch is permanently malformed, fall back to per-job getStatus() calls for jobIds you already know","Fix test mocks to include jobId in every extractResult element"],"exampleFix":"try {\n  const batch = await poller.getBatchStatus(batchId);\n} catch (e) {\n  if (e instanceof ResponseFormatError && e.message.includes(\"item is missing jobId\")) {\n    // degraded mode: poll known jobs individually\n    const jobs = await Promise.all(knownJobIds.map(id => poller.getStatus(id)));\n  }\n}","handlingStrategy":"fallback","validationCode":"function batchItemsAllHaveJobId(items: unknown[]): boolean {\n  return items.every((i) => typeof i === \"object\" && i !== null && typeof (i as any).jobId === \"string\");\n}","typeGuard":"function isBatchStatusPayload(v: unknown): v is { extractResult: Array<{ jobId: string }> } {\n  return typeof v === \"object\" && v !== null\n    && Array.isArray((v as any).extractResult)\n    && (v as any).extractResult.every(\n      (i: unknown) => typeof i === \"object\" && i !== null && typeof (i as { jobId?: unknown }).jobId === \"string\"\n    );\n}","tryCatchPattern":"try {\n  batch = await poller.getBatchStatus(batchId);\n} catch (e) {\n  if (e instanceof ResponseFormatError && e.message.includes(\"item is missing jobId\")) {\n    // fallback: degrade to per-job polling with ids you already hold\n    batch = { batchId, jobs: await Promise.all(knownJobIds.map(id => poller.getStatus(id))) };\n  } else throw e;\n}","preventionTips":["Persist per-job ids from submission so batch status is a convenience, not a dependency","Include jobId in every extractResult element of mocks","Capture raw batch payloads when the contract breaks to identify the malformed element"],"tags":["response-format","batch","contract","typescript"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}