jackwener/OpenCLI · error · CommandExecutionError

Chess.com game archive entry does not include the requested

Error message

Chess.com game archive entry does not include the requested player

What it means

mapGameRow() orients win/loss columns from the requested viewer's perspective, so the archive entry must contain the requested player on one of the sides. This throw fires when neither white.username nor black.username matches viewerUsername case-insensitively — meaning the game genuinely does not involve the requested account (wrong archive month, mistyped username, or mismatched case/alias).

Source

Thrown at clis/chess/utils.js:138

    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 || '';
    return {
        date: formatDate(game?.end_time),
        time_class: game?.time_class || '',
        rated: game?.rated === true,
        my_color: viewerIsWhite ? 'white' : 'black',
        my_rating: me?.rating ?? '',
        my_result: me?.result || '',
        opponent: opp?.username || '',
        opponent_rating: opp?.rating ?? '',
        accuracy_white: typeof game?.accuracies?.white === 'number' ? game.accuracies.white : '',
        accuracy_black: typeof game?.accuracies?.black === 'number' ? game.accuracies.black : '',
        eco,
        opening_name: openingName(eco),
        url: game?.url || '',

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Check that viewerUsername is passed and matches (case-insensitively) a player in each game — log both usernames on failure
  2. Verify the archive URL uses the same username as viewerUsername: /pub/player/{viewerUsername}/games/{yyyy}/{mm}
  3. If the account was renamed, fetch archives under the username that owned the games at the time or resolve the current handle first
  4. Filter archive entries to those containing the viewer before mapping instead of letting the mapper throw

Example fix

// before
const rows = archive.games.map((g) => mapGameRow(g, opponentName)); // wrong viewer
// after
const rows = archive.games
  .filter((g) => [g.white?.username, g.black?.username].some((u) => String(u).toLowerCase() === user.toLowerCase()))
  .map((g) => mapGameRow(g, user));
Defensive patterns

Strategy: validation

Validate before calling

const viewer = String(viewerUsername || '').toLowerCase();
const involved = [game?.white?.username, game?.black?.username]
  .some((u) => typeof u === 'string' && u.toLowerCase() === viewer);
if (!involved) {
  throw new Error(`game does not involve ${viewerUsername}; check archive URL and viewer arg`);
}

Type guard

function involvesPlayer(g, username) {
  const v = String(username || '').toLowerCase();
  return [g?.white?.username, g?.black?.username]
    .some((u) => typeof u === 'string' && u.toLowerCase() === v);
}
// usage: games.filter((g) => involvesPlayer(g, user)).map((g) => mapGameRow(g, user));

Try / catch

try {
  row = mapGameRow(game, user);
} catch (err) {
  if (String(err.message).includes('does not include the requested player')) {
    console.warn(`Skipping game between ${game?.white?.username} and ${game?.black?.username}: viewer mismatch`);
  } else { throw err; }
}

Prevention

When it happens

Trigger: Calling row()/archive listing for username A but the fetched month archive contains games of username B (wrong URL path), or the entry's usernames differ from the requested handle (renamed account, different capitalization is fine but a different name is not), or viewerUsername was omitted/empty so it matches neither side.

Common situations: Passing an empty or wrong viewerUsername argument while iterating another player's archive; requesting the wrong month URL so games returned belong to a different account; account renamed after games were played (old username in archives vs new handle requested); using the opponent's username by mistake.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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