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

  1. Log the offending game.url value and compare against the expected https://www.chess.com/game/live/<id> or /game/daily/<id> format
  2. Re-fetch the monthly archive from the pub API — archived games normally always include the canonical URL
  3. Normalize alternative URL shapes to the canonical form before calling mapGameRow
  4. 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

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


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/33669c20938754d1. Report an issue: GitHub.