{"record":{"id":"8efd373190dcb54b","repo":"can1357/oh-my-pi","slug":"the-upload-endpoint-returned-an-invalid-json-objec","errorCode":null,"errorMessage":"the upload endpoint returned an invalid JSON object","messagePattern":"the upload endpoint returned an invalid JSON object","errorType":"exception","errorClass":"LegacyDestinationError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-legacy.ts","lineNumber":133,"sourceCode":"\t\tconst value = record[key];\n\t\tif (typeof value === \"string\" && value.trim()) return value.trim();\n\t}\n\treturn undefined;\n}\n\nasync function jsonObject(\n\tdestination: BlobDestinationId,\n\tresponse: Response,\n): Promise<Readonly<Record<string, unknown>>> {\n\tlet value: unknown;\n\ttry {\n\t\tvalue = await response.json();\n\t} catch (error) {\n\t\tthrow new LegacyDestinationError(destination, \"the upload endpoint returned invalid JSON\", error);\n\t}\n\tconst record = objectValue(value);\n\tfor (const _ in record) return record;\n\tthrow new LegacyDestinationError(destination, \"the upload endpoint returned an invalid JSON object\");\n}\n\nfunction directJsonUrl(destination: BlobDestinationId, record: Readonly<Record<string, unknown>>, base: URL): string {\n\tconst nested = objectValue(record.response);\n\tconst raw =\n\t\tfirstString(record, [\"direct_url\", \"directUrl\", \"url\", \"URL\"]) ??\n\t\tfirstString(nested, [\"direct_url\", \"directUrl\", \"url\", \"URL\"]);\n\tif (!raw) throw new LegacyDestinationError(destination, \"the upload response did not include a direct image URL\");\n\treturn httpUrl(destination, raw, base);\n}\n\nfunction basicAuthorization(username: string, password: string): string {\n\treturn `Basic ${Buffer.from(`${username}:${password}`, \"utf8\").toString(\"base64\")}`;\n}\n\nfunction optionalBasicHeaders(\n\tdestination: BlobDestinationId,\n\tconfig: DestinationRuntimeConfig,","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-legacy.ts#L115-L151","documentation":"This LegacyDestinationError is thrown by jsonObject() when the response body parses as JSON but is not a non-empty plain object (arrays, null, strings, numbers, and empty objects are all rejected). The uploaders index into the parsed value with string keys, so they require an object with at least one property.","triggerScenarios":"The endpoint returns `null`, `[]`, `\"ok\"`, `42`, or `{}` — e.g. an upload API that acknowledges success with an empty JSON object or a bare array of URLs; thrown right after the invalid-JSON check in jsonObject(), consumed by the `data` helper.","commonSituations":"Replacement endpoints whose success response is `{}` or a JSON array; APIs returning JSON-RPC style arrays; servers echoing empty bodies with a JSON content-type.","solutions":["Adjust the endpoint to return a JSON object containing the upload result fields (url/direct_url)","If the endpoint returns an array or scalar, wrap it server-side in an object","Check the response body to confirm it is `{}`/array/null and compare with the schema the uploader expects"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isNonEmptyJsonObject(value: unknown): value is Record<string, unknown> {\n  return (\n    typeof value === \"object\" && value !== null && !Array.isArray(value) &&\n    Object.keys(value).length > 0\n  );\n}","tryCatchPattern":"try {\n  const result = await uploader.upload(request);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"invalid JSON object\")) {\n    // body parsed but was null/array/scalar/{} — fix server response shape\n  } else throw err;\n}","preventionTips":["Ensure upload endpoints return a JSON object with result fields, never {} or arrays","Compare your endpoint's success body with the uploader's expected schema","Add an integration test asserting the parsed response is a non-empty object","Guard against APIs that acknowledge success with empty bodies"],"tags":["network","upload","json","response-parsing"],"backgroundTag":"invalid-json-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}