{"record":{"id":"00fb303ae8a2c66d","repo":"jackwener/OpenCLI","slug":"chess-com-archives-payload-contains-an-unexpected","errorCode":null,"errorMessage":"Chess.com archives payload contains an unexpected archive URL","messagePattern":"Chess\\.com archives payload contains an unexpected archive URL","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chess/games.js","lineNumber":48,"sourceCode":"        { name: 'username', type: 'string', required: true, positional: true, help: 'Chess.com username' },\n        { name: 'limit', type: 'int', default: 10, help: `Number of recent games (1-${MAX_LIMIT})` },\n    ],\n    columns: ['date', 'time_class', 'rated', 'my_color', 'my_rating', 'my_result', 'opponent', 'opponent_rating', 'accuracy_white', 'accuracy_black', 'eco', 'opening_name', 'url'],\n    func: async (kwargs) => {\n        const username = validateUsername(kwargs.username);\n        const limit = parseLimit(kwargs.limit);\n        const archivesList = await chessApi(`/player/${encodeURIComponent(username)}/games/archives`);\n        if (!Array.isArray(archivesList.archives)) {\n            throw new CommandExecutionError('Chess.com archives payload is missing archives array');\n        }\n        const archives = archivesList.archives.slice().reverse();\n        if (archives.length === 0) {\n            throw new EmptyResultError(`Chess.com has no game archives for ${username}`);\n        }\n        const rows = [];\n        for (let i = 0; i < archives.length && i < MAX_ARCHIVE_FETCHES && rows.length < limit; i++) {\n            if (typeof archives[i] !== 'string' || !archives[i].startsWith('https://api.chess.com/pub/player/')) {\n                throw new CommandExecutionError('Chess.com archives payload contains an unexpected archive URL');\n            }\n            const monthly = await chessApi(archives[i]);\n            if (!Array.isArray(monthly.games)) {\n                throw new CommandExecutionError('Chess.com monthly archive payload is missing games array');\n            }\n            const games = monthly.games.slice().reverse();\n            for (const g of games) {\n                rows.push(mapGameRow(g, username));\n                if (rows.length >= limit) break;\n            }\n        }\n        if (rows.length === 0) {\n            throw new EmptyResultError(`Chess.com has games archives for ${username} but no games in the most recent ${MAX_ARCHIVE_FETCHES} months`);\n        }\n        return rows.slice(0, limit);\n    },\n});\n","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chess/games.js#L30-L66","documentation":"Each entry in the archives array must be a string starting with https://api.chess.com/pub/player/ before the command fetches it. If Chess.com returns an unexpected entry (non-string, relative URL, or a different host), the command throws CommandExecutionError to avoid fetching an unvalidated URL.","triggerScenarios":"archives[i] is not a string, or is a string not beginning with the expected https://api.chess.com/pub/player/ prefix — e.g. Chess.com switches to relative paths or a new domain, or the payload contains null/numeric entries.","commonSituations":"Chess.com API schema/URL changes; third-party mirrors or mocks of the API returning different URL formats; man-in-the-middle or proxy rewriting URLs.","solutions":["Check the current shape of /player/{username}/games/archives with curl to see what URL format Chess.com now returns","Update this library (or patch the prefix check) if Chess.com changed its URL scheme","Ensure no proxy is rewriting response bodies"],"exampleFix":"// before\nif (!archives[i].startsWith('https://api.chess.com/pub/player/')) throw ...;\n// after: tolerate both absolute and relative forms\nconst u = archives[i];\nconst ok = typeof u === 'string' && (u.startsWith('https://api.chess.com/pub/player/') || /^\\/pub\\/player\\//.test(u));\nif (!ok) throw new CommandExecutionError('unexpected archive URL');","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isKnownArchiveUrl(u) { return typeof u === 'string' && (u.startsWith('https://api.chess.com/pub/player/') || u.startsWith('/pub/player/')); }","tryCatchPattern":"try {\n  await gamesCmd(username);\n} catch (e) {\n  if (/unexpected archive URL/.test(e.message)) {\n    // Chess.com likely changed URL format; re-inspect /games/archives payload\n  } else throw e;\n}","preventionTips":["Periodically curl /player/{u}/games/archives to detect URL format drift","Keep the library updated so the prefix check matches current Chess.com URLs","Disable response-rewriting proxies for api.chess.com traffic"],"tags":["schema-validation","url-validation","chess-com"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}