jackwener/OpenCLI · error · CommandExecutionError

Chess.com callback returned HTTP ${resp.status} for ${url}

Error message

Chess.com callback returned HTTP ${resp.status} for ${url}

What it means

After handling 404, any other non-OK response (resp.ok false) from the Chess.com callback endpoint throws CommandExecutionError with the HTTP status. This surfaces upstream API problems — rate limiting, 5xx outages, redirects to challenge pages — as an explicit command error instead of a confusing JSON parse failure.

Source

Thrown at clis/chess/game.js:102

        'eco', 'time_control', 'rated', 'ply_count', 'url',
    ],
    func: async (kwargs) => {
        const { kind, id } = parseGameUrl(kwargs['game-url']);
        const url = `${CALLBACK_BASE}/${kind}/game/${id}`;
        let resp;
        try {
            resp = await fetch(url, { headers: { 'User-Agent': UA, accept: 'application/json' } });
        } catch (error) {
            throw new CommandExecutionError(`Failed to fetch Chess.com callback ${url}: ${error?.message || error}`);
        }
        if (!resp || typeof resp !== 'object') {
            throw new CommandExecutionError(`Chess.com callback returned an invalid response object for ${url}`);
        }
        if (resp.status === 404) {
            throw new EmptyResultError(`Chess.com has no ${kind} game with id ${id}`);
        }
        if (!resp.ok) {
            throw new CommandExecutionError(`Chess.com callback returned HTTP ${resp.status} for ${url}`);
        }
        let payload;
        try {
            payload = await resp.json();
        } catch (error) {
            throw new CommandExecutionError(`Chess.com callback returned malformed JSON for ${url}: ${error?.message || error}`);
        }
        return [summarizeGame({ kind, id, payload })];
    },
});

export const __test__ = { parseGameUrl, summarizeGame };

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the status in the message and retry after a backoff for 429/5xx
  2. Send a realistic User-Agent and avoid high request rates — the library already sets UA, so add spacing between calls
  3. For persistent 403, run from a different network/IP or use the public pubsub API endpoints instead of the callback endpoint

Example fix

// before: naive loop
for (const id of ids) await getGame(id);
// after: throttle + backoff
for (const id of ids) {
  try { await getGame(id); }
  catch (e) { if (/HTTP 429|HTTP 5/.test(e.message)) await sleep(5000); }
  await sleep(1000);
}
Defensive patterns

Strategy: retry

Validate before calling

// no pre-call validation possible; monitor status before retrying
// e.g. check https://status.chess.com or reduce request frequency

Try / catch

async function withRetry(fn, attempts = 3) {
  for (let i = 0; ; i++) {
    try { return await fn(); }
    catch (e) {
      const m = /HTTP (\d{3})/.exec(e.message || '');
      const status = m ? Number(m[1]) : 0;
      if (i < attempts - 1 && (status === 429 || status >= 500)) { await new Promise(r => setTimeout(r, 2 ** i * 1000)); continue; }
      throw e;
    }
  }
}

Prevention

When it happens

Trigger: The fetch to https://www.chess.com/callback/{kind}/game/{id} returns any status other than 2xx/404: 403 (bot challenge/Cloudflare), 429 (rate limited), 5xx (Chess.com outage).

Common situations: Hammering the API in a loop without delays (429); Chess.com serving a bot-detection page to datacenter IPs (403); temporary Chess.com incidents (500/502/503).

Related errors


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