jackwener/OpenCLI · error · CommandExecutionError
12306 rejected every known query endpoint name (${QUERY_ENDP
Error message
12306 rejected every known query endpoint name (${QUERY_ENDPOINTS.join(', ')}); the wire protocol may have changed. Last body: ${lastResponseText.slice(0, 200)} What it means
Thrown after queryLeftTickets exhausted its queue of candidate query endpoint names (queryG, queryO, queryZ, queryA plus any rotation hints) without ever getting a usable response. 12306 rotates its leftTicket endpoint name every few weeks; when none of the known names (and none suggested via c_url/302 redirects) work, this error reports all tried names plus the last 200 chars of the last response body to aid diagnosis.
Source
Thrown at clis/12306/trains.js:100
const text = await resp.text();
lastResponseText = text;
let json;
try { json = JSON.parse(text); } catch {
throw new CommandExecutionError(`12306 ${endpoint} returned non-JSON body`);
}
if (json?.c_url && typeof json.c_url === 'string') {
const rotated = await parseRotationEndpoint(resp, endpoint, text);
if (rotated && !tried.has(rotated)) {
queue.unshift(rotated);
}
continue;
}
if (Array.isArray(json?.data?.result)) {
return json.data.result;
}
throw new CommandExecutionError(`12306 ${endpoint} returned an unexpected payload shape`);
}
throw new CommandExecutionError(`12306 rejected every known query endpoint name (${QUERY_ENDPOINTS.join(', ')}); the wire protocol may have changed. Last body: ${lastResponseText.slice(0, 200)}`);
}
cli({
site: '12306',
name: 'trains',
access: 'read',
description: 'List trains between two 12306 stations on a given date (anonymous, no login required)',
domain: 'kyfw.12306.cn',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'from', positional: true, required: true, help: 'Origin station: Chinese name (北京), telecode (BJP), or pinyin (beijing)' },
{ name: 'to', positional: true, required: true, help: 'Destination station: same forms as <from>' },
{ name: 'date', required: true, help: 'Departure date in YYYY-MM-DD' },
{ name: 'limit', type: 'int', default: 50, help: `Maximum rows (1-${MAX_LIMIT})` },
],
columns: [
'code', 'from_station', 'to_station', 'start_time', 'arrive_time',View on GitHub (pinned to 49907e53dc)
Solutions
- Inspect `Last body:` in the message — it usually reveals the new protocol response or the block page; update QUERY_ENDPOINTS in clis/12306/trains.js accordingly.
- Check the live 12306 ticket-booking site's network tab for the current `/otn/leftTicket/queryX` name and add it to QUERY_ENDPOINTS.
- Update the library to the latest version, which may already track the rotated endpoint.
- Retry later if 12306 is under maintenance or heavy load (especially around ticket-release times).
- Verify network egress isn't blocking kyfw.12306.cn (test station bundle fetch first).
Example fix
// before const QUERY_ENDPOINTS = ['queryG', 'queryO', 'queryZ', 'queryA']; // after const QUERY_ENDPOINTS = ['queryG', 'queryO', 'queryZ', 'queryA', 'queryE', 'query']; // add newly rotated names discovered from the live site
Defensive patterns
Strategy: fallback
Validate before calling
// Probe endpoints before committing to a query run:
for (const ep of ['queryG', 'queryO', 'queryZ', 'queryA']) {
const r = await fetch(`https://kyfw.12306.cn/otn/leftTicket/${ep}?${probe}`);
if (r.ok) console.log('usable endpoint:', ep);
} Type guard
function isUsableEndpointResponse(json) {
return typeof json === 'object' && json !== null &&
(typeof json.c_url === 'string' || Array.isArray(json?.data?.result));
} Try / catch
try {
rows = await queryLeftTickets(cookie, from, to, date);
} catch (e) {
if (/rejected every known query endpoint/.test(e.message)) {
console.error('All endpoints exhausted. Body hint:', e.message.match(/Last body: (.*)$/)?.[1]);
// check https://kyfw.12306.cn booking page for the new queryX name and patch QUERY_ENDPOINTS
}
throw e;
} Prevention
- Update the library promptly; endpoint rotation happens every few weeks.
- Read the `Last body:` hint in the error to identify the new protocol.
- Check the live 12306 site's devtools network tab for the current leftTicket/queryX name.
- Avoid running during maintenance windows and ticket-release rushes.
When it happens
Trigger: Every fetch to `otn/leftTicket/<name>` across the whole candidate queue failed: non-ok non-302 statuses, non-JSON bodies, unexpected payload shapes, or rotation hints pointing at already-tried endpoints, so the queue drains and the while loop exits.
Common situations: 12306 rotated its query endpoint to a new letter not in QUERY_ENDPOINTS and not advertised via c_url; the library version is outdated relative to the live 12306 site; a network middlebox blocks all leftTicket calls; 12306 is in a long maintenance window returning errors for every endpoint.
Related errors
- 12306 ${endpoint} returned an unexpected payload shape
- ${label} returned invalid JSON
- 12306 queryByTrainNo returned HTTP ${resp.status}
- 12306 queryByTrainNo returned non-JSON body
- 12306 ${endpoint} returned non-JSON body
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/0977d825efb807a3.
Report an issue: GitHub.