{"record":{"id":"f1aa8cd79560d379","repo":"jackwener/OpenCLI","slug":"chess-com-callback-returned-malformed-json-for-u","errorCode":null,"errorMessage":"Chess.com callback returned malformed JSON for ${url}: ${error?.message || error}","messagePattern":"Chess\\.com callback returned malformed JSON for (.+?): (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chess/game.js","lineNumber":108,"sourceCode":"        try {\n            resp = await fetch(url, { headers: { 'User-Agent': UA, accept: 'application/json' } });\n        } catch (error) {\n            throw new CommandExecutionError(`Failed to fetch Chess.com callback ${url}: ${error?.message || error}`);\n        }\n        if (!resp || typeof resp !== 'object') {\n            throw new CommandExecutionError(`Chess.com callback returned an invalid response object for ${url}`);\n        }\n        if (resp.status === 404) {\n            throw new EmptyResultError(`Chess.com has no ${kind} game with id ${id}`);\n        }\n        if (!resp.ok) {\n            throw new CommandExecutionError(`Chess.com callback returned HTTP ${resp.status} for ${url}`);\n        }\n        let payload;\n        try {\n            payload = await resp.json();\n        } catch (error) {\n            throw new CommandExecutionError(`Chess.com callback returned malformed JSON for ${url}: ${error?.message || error}`);\n        }\n        return [summarizeGame({ kind, id, payload })];\n    },\n});\n\nexport const __test__ = { parseGameUrl, summarizeGame };\n","sourceCodeStart":90,"sourceCodeEnd":115,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chess/game.js#L90-L115","documentation":"When resp.ok is true but resp.json() throws, the library rethrows as CommandExecutionError indicating the callback returned malformed JSON. Chess.com's callback endpoint normally returns JSON, but error pages, HTML challenges, or truncated responses will fail to parse.","triggerScenarios":"The /callback/{kind}/game/{id} endpoint returns 200 with a non-JSON body: an HTML bot-challenge page, an empty body, a truncated response, or a content-type change by Chess.com.","commonSituations":"Cloudflare/bot interstitial served with status 200; network middleboxes (corporate proxies) rewriting responses; Chess.com changing the callback endpoint format; flaky connections truncating the body.","solutions":["Log or print the raw response text to see what was actually returned (wrap fetch yourself or use curl with the same User-Agent)","Retry after a delay — truncated responses are often transient","Switch to the documented public API (api.chess.com/pub/game/{id}) if the callback endpoint keeps returning HTML","Check for proxy/firewall interference on the host machine"],"exampleFix":"// before: silent assumption of JSON\nconst payload = await resp.json();\n// after: capture body for diagnosis\nconst text = await resp.text();\nlet payload;\ntry { payload = JSON.parse(text); }\ncatch (e) { console.error('Non-JSON body:', text.slice(0, 200)); throw e; }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const rows = await chessGameCmd(gameUrl);\n} catch (e) {\n  if (/malformed JSON/.test(e.message)) {\n    // retry once after delay; if persistent, capture raw body via curl for diagnosis\n    await new Promise(r => setTimeout(r, 3000));\n    return await chessGameCmd(gameUrl);\n  }\n  throw e;\n}","preventionTips":["Retry transient parse failures after a short delay","Diagnose with curl using the same User-Agent to see the raw body","Watch for Cloudflare/HTML challenge pages served with status 200","Bypass suspicious proxies/VPNs when the endpoint returns HTML"],"tags":["json","parse-error","network","bot-detection"],"backgroundTag":"malformed-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}