{"record":{"id":"a22865c5fcf66006","repo":"jackwener/OpenCLI","slug":"chess-com-callback-returned-malformed-pgn-headers","errorCode":null,"errorMessage":"Chess.com callback returned malformed PGN headers","messagePattern":"Chess\\.com callback returned malformed PGN headers","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chess/game.js","lineNumber":26,"sourceCode":"import { UA, formatDate, isPlainObject, parseGameUrl } from './utils.js';\n\nconst CALLBACK_BASE = 'https://www.chess.com/callback';\n\nfunction stringOrEmpty(value) {\n    return typeof value === 'string' ? value : '';\n}\n\nfunction scalarOrEmpty(value) {\n    return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' ? value : '';\n}\n\nexport function summarizeGame({ kind, id, payload }) {\n    if (!isPlainObject(payload) || !isPlainObject(payload.game)) {\n        throw new CommandExecutionError('Chess.com callback returned no game payload');\n    }\n    const g = payload.game;\n    if (g.pgnHeaders !== undefined && !isPlainObject(g.pgnHeaders)) {\n        throw new CommandExecutionError('Chess.com callback returned malformed PGN headers');\n    }\n    if (payload.players !== undefined && !isPlainObject(payload.players)) {\n        throw new CommandExecutionError('Chess.com callback returned malformed player metadata');\n    }\n    const players = payload.players || {};\n    const byColor = {};\n    for (const slot of ['top', 'bottom']) {\n        const p = players[slot];\n        if (p !== undefined && !isPlainObject(p)) {\n            throw new CommandExecutionError('Chess.com callback returned malformed player metadata');\n        }\n        if (p?.color) byColor[p.color] = p;\n    }\n    const white = byColor.white || {};\n    const black = byColor.black || {};\n    const headers = g.pgnHeaders || {};\n    const whiteName = stringOrEmpty(white.username) || stringOrEmpty(headers.White);\n    const blackName = stringOrEmpty(black.username) || stringOrEmpty(headers.Black);","sourceCodeStart":8,"sourceCodeEnd":44,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chess/game.js#L8-L44","documentation":"summarizeGame expects payload.game.pgnHeaders, when present, to be a plain object of PGN header key/values; this CommandExecutionError fires when it exists but has an unexpected shape (e.g. a string or array), protecting downstream header reads (White/Black/Result/Date).","triggerScenarios":"Chess.com callback returns game.pgnHeaders as a non-object (string of raw PGN, null treated as present, array) while being defined — i.e. `pgnHeaders !== undefined && !isPlainObject(pgnHeaders)`.","commonSituations":"Endpoint schema change (e.g. pgnHeaders became a raw PGN string); caching layers returning older/newer shapes; manual mock data in tests not matching the real shape.","solutions":["Inspect the actual pgnHeaders value returned and adapt parsing (e.g. parse a raw PGN string into headers).","Pin/upgrade the CLI to match the current Chess.com callback schema.","In tests, use fixtures captured from the real endpoint so shapes match.","Guard at the fetch layer: validate pgnHeaders is an object before summarizing."],"exampleFix":"// before\n// assumes pgnHeaders is always an object\nconst headers = g.pgnHeaders || {};\n// after\nif (g.pgnHeaders !== undefined && !isPlainObject(g.pgnHeaders)) {\n  throw new CommandExecutionError('Chess.com callback returned malformed PGN headers');\n}\nconst headers = g.pgnHeaders || {};","handlingStrategy":"type-guard","validationCode":"if (payload?.game?.pgnHeaders !== undefined && !isPlainObject(payload.game.pgnHeaders)) {\n  throw new Error('pgnHeaders is not an object; parse raw PGN instead');\n}","typeGuard":"const isPlainObject = (v) => Object.prototype.toString.call(v) === '[object Object]';\nconst hasValidPgnHeaders = (g) => g.pgnHeaders === undefined || isPlainObject(g.pgnHeaders);","tryCatchPattern":"try {\n  const row = summarizeGame({ kind, id, payload });\n} catch (err) {\n  if (String(err.message).includes('malformed PGN headers')) {\n    // normalize: parse raw PGN string into headers, then retry\n  } else throw err;\n}","preventionTips":["Test against live endpoint fixtures so shape changes are caught early","Normalize raw PGN strings to header objects before summarizing","Check Chess.com API changelogs after CLI upgrades"],"tags":["api-response","schema-validation","chess"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}