jackwener/OpenCLI · error · CommandExecutionError
Chess.com API returned an unexpected payload shape for ${url
Error message
Chess.com API returned an unexpected payload shape for ${url} What it means
chessApi() fetches a Chess.com pub API endpoint and, after confirming the body parses as JSON, verifies the decoded value is a plain (non-null, non-array) object before returning it. This throw means the endpoint responded with valid JSON whose top level is an array, string, number, or null instead of the expected object — i.e. an API contract change or a response from an unexpected origin. The library throws defensively so callers can assume payload.field access is safe.
Source
Thrown at clis/chess/utils.js:63
let resp;
try {
resp = await fetchImpl(url, { headers: { 'User-Agent': UA, accept: 'application/json' } });
} catch (error) {
throw new CommandExecutionError(`Failed to fetch Chess.com API ${url}: ${error?.message || error}`);
}
if (!resp || typeof resp !== 'object') {
throw new CommandExecutionError(`Chess.com API returned an invalid response object for ${url}`);
}
if (resp.status === 404) throw new EmptyResultError(`Chess.com returned 404 for ${url}`);
if (!resp.ok) throw new CommandExecutionError(`Chess.com API returned HTTP ${resp.status} for ${url}`);
let payload;
try {
payload = await resp.json();
} catch (error) {
throw new CommandExecutionError(`Chess.com API returned malformed JSON for ${url}: ${error?.message || error}`);
}
if (!isPlainObject(payload)) {
throw new CommandExecutionError(`Chess.com API returned an unexpected payload shape for ${url}`);
}
return payload;
}
/** Pull rating + record fields out of a stats sub-object (`chess_rapid` etc). */
export function summarizeStats(stats, kind) {
const k = stats?.[kind];
if (!k) return null;
if (!isPlainObject(k)) {
throw new CommandExecutionError(`Chess.com stats payload for ${kind} is not an object`);
}
if (!isOptionalPlainObject(k.last)) {
throw new CommandExecutionError(`Chess.com stats payload for ${kind}.last is not an object`);
}
if (!isOptionalPlainObject(k.best)) {
throw new CommandExecutionError(`Chess.com stats payload for ${kind}.best is not an object`);
}
if (!isOptionalPlainObject(k.record)) {View on GitHub (pinned to 49907e53dc)
Solutions
- Log the URL and JSON.stringify(payload).slice(0,200) to see the actual top-level shape
- If the payload is an array, wrap or index it before passing through, or call the correct pub endpoint that returns an object
- Update the library/endpoint path if Chess.com changed the pub API response envelope
- If using a proxy or mock, fix it to return a JSON object at the top level
Example fix
// before
const list = await chessApi('https://api.chess.com/pub/player/hikori/games/2024/01');
// after — tolerate an array envelope at the call site
let data = await chessApi(url);
if (Array.isArray(data)) data = { games: data }; Defensive patterns
Strategy: type-guard
Validate before calling
// Pre-check a fetch/JSON result before trusting chessApi
function assertObjectPayload(json) {
if (json === null || typeof json !== 'object' || Array.isArray(json)) {
throw new Error(`Unexpected top-level JSON shape: ${Array.isArray(json) ? 'array' : typeof json}`);
}
return json;
} Type guard
function isPlainObject(v) {
return v !== null && typeof v === 'object' && !Array.isArray(v);
}
// usage: if (!isPlainObject(json)) { ...handle... } Try / catch
try {
const data = await chessApi(path);
} catch (err) {
if (String(err.message).includes('unexpected payload shape')) {
console.error(`Contract drift at ${path}: inspect raw body and pin/patch the endpoint`);
} else { throw err; }
} Prevention
- Always call documented https://api.chess.com/pub/ endpoints that return a JSON object at top level
- Pin/mirror pub API responses in tests to detect contract drift early
- Never pass arbitrary absolute URLs to chessApi without checking the response contract
- Log the raw body (first ~200 chars) on shape failures for fast diagnosis
When it happens
Trigger: The URL (path joined to https://api.chess.com/pub/ or an absolute URL passed in) returns JSON that is not an object at the top level, e.g. resp.json() resolves to an array, a bare string/number, or null.
Common situations: Chess.com changes an endpoint's response envelope in a new API version; the caller passes a full URL to a non-pub endpoint (proxy, mirror, mock server) that returns a JSON array or scalar; a misconfigured test double returns [] instead of {}; CDN/interstitial returns a JSON string body.
Related errors
- No user message found in request
- 获取到的视频播放信息对象不符合预期格式
- Bilibili view API returned a malformed ${label}
- Bilibili view API did not return pages[] for --page selectio
- Bilibili view API returned a malformed pages[] entry
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/8ad7fd2945c21ef0.
Report an issue: GitHub.