jackwener/OpenCLI · error · CommandExecutionError
Chess.com game archive entry is missing a stable game URL
Error message
Chess.com game archive entry is missing a stable game URL
What it means
mapGameRow() requires every game entry to carry a stable canonical URL matching https://www.chess.com/game/live/<digits> or /game/daily/<digits>; this URL is used as the row's unique key. This throw fires when game.url is missing, not a string, or points at a different game host/format (e.g. /game/<id> without live|daily, chess.com/live/<id>, or a relative link).
Source
Thrown at clis/chess/utils.js:124
export function openingName(eco) {
if (typeof eco !== 'string' || !eco.startsWith('http')) return '';
const tail = eco.replace(/\/+$/, '').split('/').pop() || '';
if (!tail) return '';
const namePart = tail.match(/^([^.]+?)(?:-\d|\.\.\.|$)/);
const cleaned = (namePart ? namePart[1] : tail).replace(/-/g, ' ').trim();
return cleaned;
}
/**
* Map a Chess.com game record (from the monthly archive) to a flat row.
* The viewer perspective controls win/loss orientation.
*/
export function mapGameRow(game, viewerUsername) {
if (!isPlainObject(game)) {
throw new CommandExecutionError('Chess.com game archive entry is not an object');
}
if (typeof game.url !== 'string' || !/^https:\/\/www\.chess\.com\/game\/(?:live|daily)\/\d+(?:$|[/?#])/i.test(game.url)) {
throw new CommandExecutionError('Chess.com game archive entry is missing a stable game URL');
}
const white = game?.white || {};
const black = game?.black || {};
if (!isPlainObject(white) || !isPlainObject(black)) {
throw new CommandExecutionError('Chess.com game archive entry has malformed player objects');
}
if (typeof white.username !== 'string' || !white.username.trim() || typeof black.username !== 'string' || !black.username.trim()) {
throw new CommandExecutionError('Chess.com game archive entry is missing stable player identities');
}
const viewerLower = String(viewerUsername || '').toLowerCase();
const viewerIsWhite = String(white.username || '').toLowerCase() === viewerLower;
const viewerIsBlack = String(black.username || '').toLowerCase() === viewerLower;
if (!viewerIsWhite && !viewerIsBlack) {
throw new CommandExecutionError('Chess.com game archive entry does not include the requested player');
}
const me = viewerIsWhite ? white : black;
const opp = viewerIsWhite ? black : white;
const eco = game?.eco || '';View on GitHub (pinned to 49907e53dc)
Solutions
- Log the offending game.url value and compare against the expected https://www.chess.com/game/live/<id> or /game/daily/<id> format
- Re-fetch the monthly archive from the pub API — archived games normally always include the canonical URL
- Normalize alternative URL shapes to the canonical form before calling mapGameRow
- Skip entries without a stable URL if you only need rows for well-formed games
Example fix
// before
mapGameRow({ ...game, url: `https://www.chess.com/live/${game.id}` }, user);
// after
mapGameRow({ ...game, url: `https://www.chess.com/game/live/${game.id}` }, user); Defensive patterns
Strategy: validation
Validate before calling
const GAME_URL_RE = /^https:\/\/www\.chess\.com\/game\/(?:live|daily)\/\d+(?:$|[/?#])/i;
if (typeof game?.url !== 'string' || !GAME_URL_RE.test(game.url)) {
throw new Error(`game has no stable URL: ${JSON.stringify(game?.url)}`);
} Type guard
function hasStableGameUrl(g) {
return typeof g?.url === 'string' &&
/^https:\/\/www\.chess\.com\/game\/(?:live|daily)\/\d+(?:$|[/?#])/i.test(g.url);
}
// usage: games.filter(hasStableGameUrl).map((g) => mapGameRow(g, user)); Try / catch
try {
row = mapGameRow(game, user);
} catch (err) {
if (String(err.message).includes('missing a stable game URL')) {
console.warn(`Skipping game with bad url: ${game?.url}`);
} else { throw err; }
} Prevention
- Normalize any known URL variants to the /game/live/<id> or /game/daily/<id> form before mapping
- Never synthesize URLs in a different format than the canonical pub API one
- Filter entries without a stable URL instead of assuming all archive entries qualify
- Watch for Chess.com URL-scheme changes and update the regex if the format drifts
When it happens
Trigger: Archive entry from row()'s iteration has no url field, an empty string, or a URL that fails the /game/(live|daily)/<digits> regex — such as /game/live/ (no id), /live/<id>, or https://api.chess.com links.
Common situations: Chess.com changes its public game URL scheme; entries sourced from the older /chess.com/game/... format; hand-built fixtures omitting url; data scraped from a different surface (analysis links, puzzles) that has no canonical game URL.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- Chess.com game archive entry is not an object
- Chess.com game archive entry has malformed player objects
- Chess.com game archive entry is missing stable player identi
- Chess.com game archive entry does not include the requested
- Chess.com archives payload contains an unexpected archive UR
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/33669c20938754d1.
Report an issue: GitHub.