jackwener/OpenCLI · warning · EmptyResultError

lichess user: Lichess user "${username}" returned empty payl

Error message

lichess user: Lichess user "${username}" returned empty payload.

What it means

lichess user fetches a user profile and expects a JSON object body. If the payload is falsy or not an object, it throws EmptyResultError stating the user returned an empty payload. The command intentionally avoids rendering a row of nulls (silent fallback) and surfaces the problem instead.

Source

Thrown at clis/lichess/user.js:46

        'seenAt',
        'gamesAll',
        'gamesWin',
        'gamesLoss',
        'gamesDraw',
        'topPerfName',
        'topPerfRating',
        'topPerfGames',
        'fideRating',
        'country',
        'bio',
        'url',
    ],
    func: async (args) => {
        const username = requireUsername(args.username);
        const url = `${LICHESS_BASE}/api/user/${encodeURIComponent(username)}`;
        const body = await lichessFetch(url, 'lichess user');
        if (!body || typeof body !== 'object') {
            throw new EmptyResultError('lichess user', `Lichess user "${username}" returned empty payload.`);
        }
        // Lichess marks closed accounts with `disabled: true` and strips data.
        // Surface as EmptyResultError instead of a row of nulls (silent-fallback).
        if (body.disabled === true) {
            throw new EmptyResultError('lichess user', `Lichess user "${username}" is closed/disabled.`);
        }
        const perfs = body.perfs && typeof body.perfs === 'object' ? body.perfs : {};
        // Pick the perf with the most games (excluding puzzle/storm/racer ephemera).
        const playablePerfs = Object.entries(perfs).filter(([k, v]) => v && typeof v === 'object' && !['puzzle', 'storm', 'racer', 'streak'].includes(k));
        let topPerfName = null;
        let topPerfRating = null;
        let topPerfGames = null;
        for (const [name, p] of playablePerfs) {
            const games = typeof p.games === 'number' ? p.games : 0;
            if (topPerfGames == null || games > topPerfGames) {
                topPerfName = name;
                topPerfGames = games;
                topPerfRating = typeof p.rating === 'number' ? p.rating : null;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the request — empty payloads are often transient
  2. Verify the username exists at lichess.org/@/<username> in a browser
  3. Check username spelling/casing (Lichess is case-insensitive but the account must exist)
  4. If the account is closed, expect the distinct 'closed/disabled' error instead

Example fix

// before
const body = await lichessFetch(url, 'lichess user');
// after
let body = await lichessFetch(url, 'lichess user');
if (!body || typeof body !== 'object') body = await lichessFetch(url, 'lichess user'); // retry once
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight reachability
const head = await fetch('https://lichess.org/api/user/ornicar');
if (!head.ok) console.warn('Lichess API unhealthy — expect empty payloads');

Type guard

const isUserProfile = (b) => b !== null && typeof b === 'object' && typeof b.username === 'string';

Try / catch

try {
  await lichessUser(username);
} catch (e) {
  if (e.name === 'EmptyResultError' && String(e.message).includes('empty payload')) {
    await sleep(1000); /* retry once */
  } else throw e;
}

Prevention

When it happens

Trigger: Calling lichess user with a username that Lichess resolves to an empty/non-object body (proxy or cache stripping the body), a transient API response with no JSON, or validation passing but the endpoint returning null.

Common situations: Intermittent Lichess API/CDN issues; usernames with unusual casing resolved oddly; network middleware (proxy) truncating responses; user in the middle of username change.

Related errors


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