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

  1. 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.
  2. Check the live 12306 ticket-booking site's network tab for the current `/otn/leftTicket/queryX` name and add it to QUERY_ENDPOINTS.
  3. Update the library to the latest version, which may already track the rotated endpoint.
  4. Retry later if 12306 is under maintenance or heavy load (especially around ticket-release times).
  5. 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

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


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