jackwener/OpenCLI · error · CommandExecutionError

12306 queryByTrainNo returned HTTP ${resp.status}

Error message

12306 queryByTrainNo returned HTTP ${resp.status}

What it means

queryStops calls 12306's /otn/czxx/queryByTrainNo endpoint (with minted session cookies, UA, and Referer) to list the stops of a train for the `stops` command. When the HTTP response status is not ok (e.g. 302 to a login page, 403 anti-bot block, 500 server error, 502 gateway error), the library throws this CommandExecutionError containing the status code. It indicates the upstream request failed at the HTTP layer rather than the data layer.

Source

Thrown at clis/12306/train.js:25

 */
import { cli, Strategy } from '@jackwener/opencli/registry';
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
import { fetchStationBundle, mintSession, resolveStation, validateDate } from './utils.js';

const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0 Safari/537.36';
const TRAIN_NO_RE = /^[0-9A-Za-z]{8,18}$/;

async function queryStops(cookieHeader, trainNo, fromCode, toCode, date, fetchImpl = fetch) {
    const url = `https://kyfw.12306.cn/otn/czxx/queryByTrainNo?train_no=${trainNo}&from_station_telecode=${fromCode}&to_station_telecode=${toCode}&depart_date=${date}`;
    const resp = await fetchImpl(url, {
        headers: {
            'User-Agent': UA,
            'Referer': 'https://kyfw.12306.cn/otn/leftTicket/init',
            'Cookie': cookieHeader,
        },
    });
    if (!resp.ok) {
        throw new CommandExecutionError(`12306 queryByTrainNo returned HTTP ${resp.status}`);
    }
    let json;
    try {
        json = await resp.json();
    } catch {
        throw new CommandExecutionError('12306 queryByTrainNo returned non-JSON body');
    }
    if (json?.status !== true || !Array.isArray(json?.data?.data)) {
        throw new CommandExecutionError(`12306 queryByTrainNo returned an unexpected payload shape`);
    }
    return json.data.data;
}

cli({
    site: '12306',
    name: 'train',
    access: 'read',
    description: 'List every station a 12306 train calls at, with arrival / departure / stopover time (anonymous, no login required)',

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry after a delay with backoff — transient 5xx/anti-bot blocks usually clear within seconds to minutes.
  2. Re-mint the session (the CLI does this per command) and retry once; a stale cookie is a common cause of 302/403.
  3. Reduce query frequency / add jitter between calls to avoid rate-limit and WAF blocks.
  4. Check the status code in the message: 403/302 points to anti-bot or cookie issues, 5xx points to 12306 server problems.
  5. Verify network path (proxy/VPN) isn't intercepting kyfw.12306.cn.

Example fix

// before (tight loop, gets blocked)
for (const t of trains) await stops(t);
// after (backoff + retry)
for (const t of trains) {
  try { await stops(t); }
  catch (e) { if (/HTTP (302|403|5\d\d)/.test(e.message)) { await sleep(2000); await stops(t); } }
  await sleep(500);
}
Defensive patterns

Strategy: retry

Try / catch

const withRetry = async (fn, tries = 3) => {
  for (let i = 0; i < tries; i++) {
    try { return await fn(); }
    catch (e) {
      const m = /queryByTrainNo returned HTTP (\d+)/.exec(e.message);
      if (!m || !/^(302|403|429|5\d\d)$/.test(m[1]) || i === tries - 1) throw e;
      await new Promise((r) => setTimeout(r, 1000 * 2 ** i));
    }
  }
};
const stopsData = await withRetry(() => stops(trainNo));

Prevention

When it happens

Trigger: Expired or invalid session cookie causing 12306 to redirect/respond 302/403; 12306 rate-limiting or WAF-blocking the client (no valid JSESSIONID route cookie); 12306 server-side 5xx during peak booking times; network proxy returning an error page.

Common situations: Burst-querying trains and tripping 12306's anti-scraping defenses; running during holiday ticket rushes when 12306 degrades; cookies from mintSession not accepted (datacenter IP blocked); corporate proxy intercepting HTTPS.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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