{"record":{"id":"fd8c60c5837e766d","repo":"chroma-core/chroma","slug":"unprocessable-entity-response-statustext","errorCode":null,"errorMessage":"Unprocessable Entity: ${response.statusText}","messagePattern":"Unprocessable Entity: (.+?)","errorType":"http","errorClass":"ChromaClientError","httpStatus":422,"severity":"error","filePath":"clients/new-js/packages/chromadb/src/chroma-fetch.ts","lineNumber":127,"sourceCode":"      try {\n        const body = await response.json();\n        if (\n          body &&\n          body.message &&\n          (body.message.startsWith(\"Quota exceeded\") ||\n            body.message.startsWith(\"Billing limit exceeded\"))\n        ) {\n          throw new ChromaQuotaExceededError(body?.message);\n        }\n        throw new ChromaClientError(body?.message || \"Unprocessable Entity\");\n      } catch (error) {\n        if (\n          error instanceof ChromaQuotaExceededError ||\n          error instanceof ChromaClientError\n        ) {\n          throw error;\n        }\n        throw new ChromaClientError(\n          `Unprocessable Entity: ${response.statusText}`,\n        );\n      }\n    case 429:\n      const rateLimitBody = await getErrorBody(response);\n      if (rateLimitBody.error === \"Backoff\") {\n        throw new ChromaBackoffError(\n          rateLimitBody.message || \"Backoff and retry\",\n        );\n      }\n      throw new ChromaRateLimitError(\"Rate limit exceeded\");\n  }\n\n  const errorMessage = await getErrorMessage(response);\n  throw new ChromaServerError(errorMessage);\n};\n","sourceCodeStart":109,"sourceCodeEnd":144,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/clients/new-js/packages/chromadb/src/chroma-fetch.ts#L109-L144","documentation":"Thrown by chromaFetch (chroma-fetch.ts:127) as a ChromaClientError when the server returns 422 and even reading/parsing the body fails (response.json() throws and the caught error is not already a Chroma error). The message is synthesized as 'Unprocessable Entity: <statusText>', meaning content-level rejection with an unreadable body — typically an HTML error page from a proxy rather than Chroma's JSON.","triggerScenarios":"A reverse proxy (nginx, ingress, Cloudflare) in front of Chroma intercepting the request and returning a 422 HTML/text page; truncated responses; content-encoding corruption making the body unparseable.","commonSituations":"Self-hosted Chroma behind an API gateway with body-validation WAF rules; proxies rejecting large embedding payloads; middleboxes mangling responses.","solutions":["Bypass the proxy temporarily (hit the Chroma port directly) to confirm the 422 originates from Chroma itself.","Check proxy/WAF logs and rules — especially request-size limits and body validation that can emit 422.","Shrink the request batch size if large payloads trip the proxy.","Reproduce with curl through the same proxy to capture the actual body."],"exampleFix":"# before: nginx WAF returns HTML 422 for large batches\nawait collection.add(bigBatch); // 'Unprocessable Entity: ...'\n\n# after: chunk the payload so it passes the proxy\nfor (const chunk of chunkBatch(bigBatch, 5000)) {\n  await collection.add(chunk);\n}","handlingStrategy":"try-catch","validationCode":"const MAX_BODY = 4 * 1024 * 1024; // keep under proxy body limits\nif (JSON.stringify(batch).length > MAX_BODY) {\n  throw new Error(\"batch too large for proxy; split before sending\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  await collection.add(batch);\n} catch (e) {\n  if (e instanceof ChromaClientError && e.message.startsWith(\"Unprocessable Entity:\")) {\n    // body was not JSON: suspect proxy/WAF interception; curl through the same hop to confirm\n  }\n  throw e;\n}","preventionTips":["Point clients directly at the Chroma service port, or whitelist it in WAF/proxy rules.","Keep request bodies well under proxy size limits by batching conservatively.","Capture proxy access logs alongside app logs to attribute 422s to the right tier."],"tags":["http-422","proxy","unparseable-body","gateway"],"backgroundTag":"proxy-intercepted-response","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}