{"record":{"id":"cb95780b693071fa","repo":"jackwener/OpenCLI","slug":"chess-com-game-archive-entry-is-missing-stable-pla","errorCode":null,"errorMessage":"Chess.com game archive entry is missing stable player identities","messagePattern":"Chess\\.com game archive entry is missing stable player identities","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chess/utils.js","lineNumber":132,"sourceCode":"\n/**\n * Map a Chess.com game record (from the monthly archive) to a flat row.\n * The viewer perspective controls win/loss orientation.\n */\nexport function mapGameRow(game, viewerUsername) {\n    if (!isPlainObject(game)) {\n        throw new CommandExecutionError('Chess.com game archive entry is not an object');\n    }\n    if (typeof game.url !== 'string' || !/^https:\\/\\/www\\.chess\\.com\\/game\\/(?:live|daily)\\/\\d+(?:$|[/?#])/i.test(game.url)) {\n        throw new CommandExecutionError('Chess.com game archive entry is missing a stable game URL');\n    }\n    const white = game?.white || {};\n    const black = game?.black || {};\n    if (!isPlainObject(white) || !isPlainObject(black)) {\n        throw new CommandExecutionError('Chess.com game archive entry has malformed player objects');\n    }\n    if (typeof white.username !== 'string' || !white.username.trim() || typeof black.username !== 'string' || !black.username.trim()) {\n        throw new CommandExecutionError('Chess.com game archive entry is missing stable player identities');\n    }\n    const viewerLower = String(viewerUsername || '').toLowerCase();\n    const viewerIsWhite = String(white.username || '').toLowerCase() === viewerLower;\n    const viewerIsBlack = String(black.username || '').toLowerCase() === viewerLower;\n    if (!viewerIsWhite && !viewerIsBlack) {\n        throw new CommandExecutionError('Chess.com game archive entry does not include the requested player');\n    }\n    const me = viewerIsWhite ? white : black;\n    const opp = viewerIsWhite ? black : white;\n    const eco = game?.eco || '';\n    return {\n        date: formatDate(game?.end_time),\n        time_class: game?.time_class || '',\n        rated: game?.rated === true,\n        my_color: viewerIsWhite ? 'white' : 'black',\n        my_rating: me?.rating ?? '',\n        my_result: me?.result || '',\n        opponent: opp?.username || '',","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chess/utils.js#L114-L150","documentation":"mapGameRow() requires a non-empty username string for both the white and black players, since identities anchor the row and the viewer-orientation logic. This throw fires when either side's username is missing, not a string, or an empty/whitespace-only string, even though the enclosing player object itself was a plain object.","triggerScenarios":"Archive entry with white.username or black.username absent, empty (''), whitespace-only, or a non-string (number/undefined) when mapping rows for a player via row().","commonSituations":"Fixtures with partially filled player objects; a data pipeline that dropped username fields during normalization; very old or corrupt archive entries; callers passing game data from a custom endpoint that omits usernames for anonymous guests.","solutions":["Re-fetch the archive entry from the pub API, which always includes usernames for completed games","Fix the normalization step that strips or renames username","Validate entries before mapping: drop any where typeof g.white?.username !== 'string' or the value is blank","Correct fixtures to include non-empty username strings on both sides"],"exampleFix":"// before\nconst game = { url, white: { rating: 1500 }, black: { username: 'magnus' } };\n// after\nconst game = { url, white: { username: 'hikaru', rating: 1500 }, black: { username: 'magnus', rating: 2850 } };","handlingStrategy":"validation","validationCode":"for (const side of ['white', 'black']) {\n  const u = game?.[side]?.username;\n  if (typeof u !== 'string' || !u.trim()) {\n    throw new Error(`game.${side}.username must be a non-empty string`);\n  }\n}","typeGuard":"function hasPlayerIdentities(g) {\n  const ok = (p) => typeof p?.username === 'string' && p.username.trim().length > 0;\n  return ok(g?.white) && ok(g?.black);\n}","tryCatchPattern":"try {\n  row = mapGameRow(game, user);\n} catch (err) {\n  if (String(err.message).includes('missing stable player identities')) {\n    console.warn('Skipping entry: player username missing or blank');\n  } else { throw err; }\n}","preventionTips":["Never strip or rename the username field during normalization","Ensure both sides have non-empty username strings in fixtures","Filter entries lacking identities before mapping rows","Prefer fresh pub archive data over hand-transformed copies"],"tags":["api","schema-validation","chess-com","games"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}