can1357/oh-my-pi · error · AuthBrokerError
Auth broker returned malformed JSON
Error message
Auth broker returned malformed JSON
What it means
#parseJson attempts JSON.parse on every successful (2xx/304) response body before schema validation (client.ts:423-433). If the body is not valid JSON — including empty-string bodies (which are mapped to null and then typically fail schema validation downstream instead) — the client wraps the SyntaxError as AuthBrokerError "Auth broker returned malformed JSON" with the raw text as `body` and the parse error as `cause`. Note this applies to the request path; the SSE stream path has its own separate malformed-JSON error.
Source
Thrown at packages/ai/src/auth-broker/client.ts:427
): Promise<t> {
const response = await this.#fetchRaw(method, path, opts);
const text = await response.text();
const raw = this.#parseJson(text, response.status);
const validated = RESPONSE_SCHEMAS[opts.schema](raw);
if (validated instanceof type.errors) {
throw new AuthBrokerError("Auth broker response failed schema validation", {
status: response.status,
body: validated.summary,
});
}
return validated as t;
}
#parseJson(text: string, status: number): unknown {
try {
return text.length === 0 ? null : JSON.parse(text);
} catch (parseError) {
throw new AuthBrokerError("Auth broker returned malformed JSON", {
status,
body: text,
cause: parseError,
});
}
}
async #fetchRaw(
method: "GET" | "POST" | "DELETE",
path: string,
opts: {
auth?: boolean;
body?: unknown;
signal?: AbortSignal;
headers?: Record<string, string>;
timeoutMs?: number;
},
): Promise<Response> {View on GitHub (pinned to 9690622007)
Solutions
- Inspect err.body — it contains the raw text the server sent; if it's HTML you're being intercepted (proxy, captive portal, auth wall).
- Check err.cause for the JSON.parse position/message to identify truncation vs. wrong content type.
- Fix or bypass the intercepting proxy/network path; ensure the broker URL is directly reachable from the client.
- Verify the broker sets Content-Type: application/json and emits exactly one JSON document with no trailing data.
- Catch the error and fall back to fetchSnapshot()/retry if the broker is flaky under load.
Example fix
// before
const summary = await client.fetchClientUsageSummary();
// after
try {
const summary = await client.fetchClientUsageSummary();
} catch (err) {
if (err instanceof AuthBrokerError && err.message === "Auth broker returned malformed JSON") {
logger.error("broker returned non-JSON body", { status: err.status, snippet: err.body?.slice(0, 200) });
throw new Error("Auth broker response was not JSON — check proxy/network path");
}
throw err;
} Defensive patterns
Strategy: try-catch
Type guard
function isMalformedJsonError(err: unknown): err is AuthBrokerError {
return err instanceof AuthBrokerError && err.message === "Auth broker returned malformed JSON";
} Try / catch
try {
result = await client.healthz();
} catch (err) {
if (isMalformedJsonError(err)) {
logger.error("non-JSON broker response", { status: err.status, snippet: err.body?.slice(0, 200), cause: err.cause });
throw new Error("Auth broker returned non-JSON — check for proxy/captive-portal interception");
}
throw err;
} Prevention
- Inspect err.body for HTML — its presence means a proxy or portal intercepted the request; bypass or fix the network path.
- Reach the broker directly (allowlist its URL) rather than through rewriting proxies or Wi-Fi captive portals.
- Ensure the broker always emits Content-Type: application/json with exactly one JSON document, even for errors.
- Check err.cause for JSON.parse offsets to detect truncated bodies from flaky connections, and raise proxy buffering limits if needed.
When it happens
Trigger: Any #request-backed method (healthz, fetchUsage, fetchUsageHistory, reportClientUsage, fetchClientUsageSummary, notifyUsageStale, credential upload/block/disable/refresh/blocks-delete, listDisabledCredentials) returning 2xx with a body that JSON.parse cannot parse: HTML error pages, plain-text messages, truncated JSON, BOM-prefixed bodies, or NDJSON instead of a single JSON document.
Common situations: Reverse proxy or captive portal injecting an HTML interstitial/login page on a 200; misconfigured server returning plain text; response truncated by a proxy or connection reset mid-body; gzip/transfer-encoding mishandling producing garbage bytes; a custom broker writing NDJSON or trailing junk after the JSON value.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Auth broker response failed schema validation
- AuthBrokerStreamUnsupportedError
- Cowork transport received a response without an HTTP status.
- Auth broker returned no snapshot
- Auth broker returned no initial snapshot
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3b12afe752e38801.
Report an issue: GitHub.