jackwener/OpenCLI · error · CommandExecutionError

Chess.com stats payload for ${kind}.last is not an object

Error message

Chess.com stats payload for ${kind}.last is not an object

What it means

Within summarizeStats(), the k.last field (most recent rating snapshot) may be absent, but if present it must be a plain object containing e.g. rating/date. This throw fires when k.last exists but is a non-object value (string, number, array). It is a strict shape check so downstream k.last?.rating access behaves predictably.

Source

Thrown at clis/chess/utils.js:76

        payload = await resp.json();
    } catch (error) {
        throw new CommandExecutionError(`Chess.com API returned malformed JSON for ${url}: ${error?.message || error}`);
    }
    if (!isPlainObject(payload)) {
        throw new CommandExecutionError(`Chess.com API returned an unexpected payload shape for ${url}`);
    }
    return payload;
}

/** Pull rating + record fields out of a stats sub-object (`chess_rapid` etc). */
export function summarizeStats(stats, kind) {
    const k = stats?.[kind];
    if (!k) return null;
    if (!isPlainObject(k)) {
        throw new CommandExecutionError(`Chess.com stats payload for ${kind} is not an object`);
    }
    if (!isOptionalPlainObject(k.last)) {
        throw new CommandExecutionError(`Chess.com stats payload for ${kind}.last is not an object`);
    }
    if (!isOptionalPlainObject(k.best)) {
        throw new CommandExecutionError(`Chess.com stats payload for ${kind}.best is not an object`);
    }
    if (!isOptionalPlainObject(k.record)) {
        throw new CommandExecutionError(`Chess.com stats payload for ${kind}.record is not an object`);
    }
    const record = isPlainObject(k.record) ? k.record : {};
    return {
        kind: kind.replace(/^chess_/, ''),
        rating_current: k.last?.rating ?? '',
        rating_best: k.best?.rating ?? '',
        wins: record.win ?? '',
        losses: record.loss ?? '',
        draws: record.draw ?? '',
    };
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-fetch fresh stats from the pub API; the documented shape has last as an object with rating/date
  2. Inspect the offending value (typeof k.last) and fix the producer that flattened it
  3. If building fixtures/mocks, use last: { rating: 1500, date: 1700000000 }
  4. Patch the mapping layer if Chess.com altered the last field's structure

Example fix

// before
stats.chess_rapid.last = 1500; // flattened
// after
stats.chess_rapid.last = { rating: 1500, date: Math.floor(Date.now() / 1000) };
Defensive patterns

Strategy: validation

Validate before calling

const last = stats?.[kind]?.last;
if (last != null && (typeof last !== 'object' || Array.isArray(last))) {
  throw new Error(`${kind}.last must be an object like { rating, date }`);
}

Type guard

function hasValidLast(k) {
  return k?.last == null ||
    (typeof k.last === 'object' && !Array.isArray(k.last) && typeof k.last.rating === 'number');
}

Try / catch

try {
  const row = summarizeStats(stats, 'chess_rapid');
} catch (err) {
  if (String(err.message).includes('.last is not an object')) {
    console.warn('last section malformed; refetching stats');
    stats = await (await fetch(`${API_BASE}/player/${user}/stats`)).json();
  } else { throw err; }
}

Prevention

When it happens

Trigger: Payload from /pub/player/{username}/stats where stats[kind].last is a non-object truthy value, e.g. last: "1500" or last: [..], passed via rows() or row().

Common situations: Transformed/normalized cache where last was flattened to a scalar rating; schema change on Chess.com; test fixture built with wrong nesting; a spreadsheet/CSV round-trip turned the nested object into a scalar.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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