Handling errors
Stop on the first broken game, or skip it and keep going.
Real-world PGN files are messy. Somewhere in those two million games there's usually one with a move that doesn't make sense. You choose what happens when Chessalyzer meets it: stop immediately, or skip the game and keep going.
To try both, imagine our example file with one broken game appended — 2. Nf9 is not a square a knight can reach:
[Event "Broken game"]
[White "blunderbuss"]
[Black "cleopatra"]
[Result "*"]
1. e4 e5 2. Nf9 0-1Stop on the first broken game (default)
By default, analyzePGN throws as soon as a game can't be replayed. That's what you want while developing — you notice bad data immediately:
import { analyzePGN, isReplayError } from 'chessalyzer';
try {
await analyzePGN('games.pgn', { trackers: [tiles] });
} catch (err) {
if (isReplayError(err)) {
console.error(`Game ${err.gameIndex}, move ${err.moveIndex}: ${err.san}`);
// Game 3, move 2: Nf9
}
throw err;
}isReplayError narrows the thrown value so you can read gameIndex (which game, zero-based), moveIndex (which half-move), san, and a machine-readable reason.
Skip bad games and keep going
For batch runs over mostly-good data (think Lichess database dumps), aborting on one bad apple wastes the whole run. onError: 'skip-game' collects the failures and finishes the rest:
const result = await analyzePGN('games.pgn', {
trackers: [tiles],
onError: 'skip-game',
});The three good games are fully processed, and the result tells you exactly what was skipped:
{
gameCount: 3,
moveCount: 19,
skippedGames: 1,
errors: [
{
code: 'replay',
gameIndex: 3,
moveIndex: 2,
san: 'Nf9',
reason: 'IllegalMove',
message: 'w: No piece for move N to (7,6) found!',
},
],
runs: [{ gameCount: 3, moveCount: 19, skippedGames: 1, errors: [/* same entry */] }],
}(perf.durationMs and perf.movesPerSecond omitted — they're there too inside result.perf. Each entry in runs mirrors its own skippedGames/errors when you use multiple runs, omitted when zero.)
Two limits to know: errors holds at most 100 entries — past that, result.errorsTruncated is true and counting continues silently — and Chessalyzer never prints anything by itself, so what happens with these errors is entirely your call.
Errors only surface when games are replayed
Replay trusts your PGN — it applies moves, but it does not check them for legality, and in a
count-only run (no move trackers) games aren't replayed at all, so a broken move may pass
unnoticed. That's the deliberate speed/strictness trade-off. If you're cleaning unknown data,
attach any move tracker (or set replay: 'board') to make sure every move is actually decoded.