can1357/oh-my-pi · error · Error

${context} returned an invalid JSON object

Error message

${context} returned an invalid JSON object

What it means

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.

Source

Thrown at packages/coding-agent/src/blob-broker/uploaders-object-storage.ts:431

				resourcePath,
				uploadRequest.bytes.byteLength,
				uploadRequest.mimeType,
				cacheControl,
			);
			await expectOk(await request(url, { method: "PUT", headers, body: uploadRequest.bytes }), destination);
			const deleteHeaders = await azureHeaders("DELETE", accountName, accountKey, resourcePath, 0);
			const publicUrl = publicBaseUrl ? publicObjectUrl(publicBaseUrl, key) : url.toString();
			return publication(destination, uploadRequest, publicUrl, {
				remoteId: key,
				delete: { method: "DELETE", url: url.toString(), headers: deleteHeaders },
			});
		},
	};
}

function asRecord(value: unknown, context: string): Record<string, unknown> {
	if (typeof value !== "object" || value === null || Array.isArray(value)) {
		throw new Error(`${context} returned an invalid JSON object`);
	}
	return value as Record<string, unknown>;
}

function requiredStringField(value: unknown, field: string, context: string): string {
	const result = asRecord(value, context)[field];
	if (typeof result !== "string" || result.length === 0) throw new Error(`${context} omitted ${field}`);
	return result;
}

function optionalStringField(value: unknown, field: string): string | undefined {
	if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
	const result = (value as Record<string, unknown>)[field];
	return typeof result === "string" ? result : undefined;
}

async function sha1Hex(bytes: Uint8Array): Promise<string> {
	const digest = new Uint8Array(await crypto.subtle.digest("SHA-1", strictBytes(bytes)));

View on GitHub (pinned to 9690622007)

Solutions

  1. Log the raw response body for the failing call named in `context` to see what was actually returned.
  2. Verify the API endpoint URL is correct and not intercepted by a proxy/ captive portal.
  3. Confirm the API version in the URL (e.g. /b2api/v2/) matches the payload the service returns.
  4. If the response is an HTML error page, resolve the underlying HTTP error (auth, rate limit) first.

Example fix

// before
const value = await response.json(); // may be a string or array
// after
const value = await response.json();
if (typeof value !== "object" || value === null || Array.isArray(value)) {
  throw new Error(`unexpected body: ${JSON.stringify(value).slice(0, 200)}`);
}
Defensive patterns

Strategy: type-guard

Validate before calling

const raw = await response.text();
let parsed: unknown;
try { parsed = JSON.parse(raw); } catch { throw new Error(`non-JSON body from ${url}: ${raw.slice(0, 120)}`); }
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
  throw new Error(`expected JSON object from ${url}, got ${Array.isArray(parsed) ? "array" : typeof parsed}`);
}

Type guard

function isJsonObject(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}

Try / catch

try {
  const auth = await authorizeB2(config);
} catch (err) {
  if (err instanceof Error && err.message.includes("returned an invalid JSON object")) {
    logger.error("object-storage API returned non-object JSON; check proxy/endpoint", { err });
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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).

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/f05415a6894fa2bc. Report an issue: GitHub.