{"record":{"id":"f05415a6894fa2bc","repo":"can1357/oh-my-pi","slug":"context-returned-an-invalid-json-object","errorCode":null,"errorMessage":"${context} returned an invalid JSON object","messagePattern":"(.+?) returned an invalid JSON object","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-object-storage.ts","lineNumber":431,"sourceCode":"\t\t\t\tresourcePath,\n\t\t\t\tuploadRequest.bytes.byteLength,\n\t\t\t\tuploadRequest.mimeType,\n\t\t\t\tcacheControl,\n\t\t\t);\n\t\t\tawait expectOk(await request(url, { method: \"PUT\", headers, body: uploadRequest.bytes }), destination);\n\t\t\tconst deleteHeaders = await azureHeaders(\"DELETE\", accountName, accountKey, resourcePath, 0);\n\t\t\tconst publicUrl = publicBaseUrl ? publicObjectUrl(publicBaseUrl, key) : url.toString();\n\t\t\treturn publication(destination, uploadRequest, publicUrl, {\n\t\t\t\tremoteId: key,\n\t\t\t\tdelete: { method: \"DELETE\", url: url.toString(), headers: deleteHeaders },\n\t\t\t});\n\t\t},\n\t};\n}\n\nfunction asRecord(value: unknown, context: string): Record<string, unknown> {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n\t\tthrow new Error(`${context} returned an invalid JSON object`);\n\t}\n\treturn value as Record<string, unknown>;\n}\n\nfunction requiredStringField(value: unknown, field: string, context: string): string {\n\tconst result = asRecord(value, context)[field];\n\tif (typeof result !== \"string\" || result.length === 0) throw new Error(`${context} omitted ${field}`);\n\treturn result;\n}\n\nfunction optionalStringField(value: unknown, field: string): string | undefined {\n\tif (typeof value !== \"object\" || value === null || Array.isArray(value)) return undefined;\n\tconst result = (value as Record<string, unknown>)[field];\n\treturn typeof result === \"string\" ? result : undefined;\n}\n\nasync function sha1Hex(bytes: Uint8Array): Promise<string> {\n\tconst digest = new Uint8Array(await crypto.subtle.digest(\"SHA-1\", strictBytes(bytes)));","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-object-storage.ts#L413-L449","documentation":"asRecord narrows an unknown parsed-JSON value to a plain object and throws `${context} returned an invalid JSON object` when the value is not a non-array object. It is used to validate API responses (B2 authorization, bucket listings, upload results) before field extraction, so malformed responses fail with a descriptive message naming which API stage misbehaved.","triggerScenarios":"An object-storage API call (b2_authorize_account, b2_list_buckets, upload result parsing) returns JSON that parses to a non-object — a string, number, boolean, array, or null — e.g. a proxy returning a plain-text body, or an endpoint returning a top-level JSON array.","commonSituations":"Corporate proxy or captive portal returns HTML/text that a lenient JSON parse mangled; B2 API version mismatch returning an error array; hitting the wrong URL (e.g. an auth endpoint that returns a bare token string).","solutions":["Log the raw response body for the failing call named in `context` to see what was actually returned.","Verify the API endpoint URL is correct and not intercepted by a proxy/ captive portal.","Confirm the API version in the URL (e.g. /b2api/v2/) matches the payload the service returns.","If the response is an HTML error page, resolve the underlying HTTP error (auth, rate limit) first."],"exampleFix":"// before\nconst value = await response.json(); // may be a string or array\n// after\nconst value = await response.json();\nif (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n  throw new Error(`unexpected body: ${JSON.stringify(value).slice(0, 200)}`);\n}","handlingStrategy":"type-guard","validationCode":"const raw = await response.text();\nlet parsed: unknown;\ntry { parsed = JSON.parse(raw); } catch { throw new Error(`non-JSON body from ${url}: ${raw.slice(0, 120)}`); }\nif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n  throw new Error(`expected JSON object from ${url}, got ${Array.isArray(parsed) ? \"array\" : typeof parsed}`);\n}","typeGuard":"function isJsonObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const auth = await authorizeB2(config);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"returned an invalid JSON object\")) {\n    logger.error(\"object-storage API returned non-object JSON; check proxy/endpoint\", { err });\n  } else throw err;\n}","preventionTips":["log raw response bodies for object-storage calls in debug mode","bypass or configure proxies for B2/S3 endpoints","verify API version paths (/b2api/v2/) against current docs","add an integration check that authorizes against the real endpoint before first upload"],"tags":["object-storage","json-parsing","api-response","b2"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}