{"record":{"id":"187ede51eb77edc5","repo":"can1357/oh-my-pi","slug":"destination-returned-invalid-json","errorCode":null,"errorMessage":"${destination} returned invalid JSON","messagePattern":"(.+?) returned invalid JSON","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-image-hosts.ts","lineNumber":42,"sourceCode":"\tconsumerSecret: string;\n\ttoken: string;\n\ttokenSecret: string;\n}\n\nfunction responseRecord(value: unknown, destination: BlobDestinationId): Record<string, unknown> {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) {\n\t\tthrow new Error(`${destination} returned an invalid response`);\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nasync function jsonResponse(response: Response, destination: BlobDestinationId): Promise<Record<string, unknown>> {\n\tawait expectOk(response, destination);\n\tlet value: unknown;\n\ttry {\n\t\tvalue = await response.json();\n\t} catch {\n\t\tthrow new Error(`${destination} returned invalid JSON`);\n\t}\n\treturn responseRecord(value, destination);\n}\n\nfunction nestedRecord(\n\trecord: Record<string, unknown>,\n\tkey: string,\n\tdestination: BlobDestinationId,\n): Record<string, unknown> {\n\treturn responseRecord(record[key], destination);\n}\n\nfunction requiredString(record: Record<string, unknown>, key: string, destination: BlobDestinationId): string {\n\tconst value = record[key];\n\tif (typeof value !== \"string\" || value.length === 0) {\n\t\tthrow new Error(`${destination} response did not include ${key}`);\n\t}\n\treturn value;","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-image-hosts.ts#L24-L60","documentation":"After a successful HTTP status, jsonResponse calls response.json() to decode the body. If the body is not valid JSON (HTML error page, empty body, truncated stream), this error is thrown naming the destination. Note it is thrown only after expectOk passes, so the host returned a success status with a non-JSON body.","triggerScenarios":"Response has status 2xx but body is HTML or empty; body is malformed/truncated JSON; response.json() throws due to content encoding issues.","commonSituations":"Image host returning an HTML maintenance page with 200; proxy/CDN stripping the body; API deprecation redirecting to an HTML page; network interruption truncating the response.","solutions":["Inspect the raw body with response.text() to confirm what the host sent","Check the host's status page or API changelog for endpoint changes","Verify credentials and endpoint URL are current for the failing destination","Add a fallback uploader so a single host's bad response doesn't abort the upload"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const body = await response.text();\nJSON.parse(body); // fail early with the raw body visible if it is not JSON","typeGuard":null,"tryCatchPattern":"try {\n  return await broker.publish(request);\n} catch (err) {\n  if (err.message.endsWith(\"returned invalid JSON\")) {\n    logger.warn(\"image host returned non-JSON success response\", { destination });\n    return null;\n  }\n  throw err;\n}","preventionTips":["Capture response.text() before .json() so failed bodies are inspectable","Watch for hosts serving HTML maintenance pages with 200 status","Configure fallback destinations in the blob broker","Monitor host status pages for API/CDN incidents"],"tags":["json","http-response","parse-error","image-host"],"backgroundTag":"invalid-json-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}