{"record":{"id":"0977d825efb807a3","repo":"jackwener/OpenCLI","slug":"12306-rejected-every-known-query-endpoint-name","errorCode":null,"errorMessage":"12306 rejected every known query endpoint name (${QUERY_ENDPOINTS.join(', ')}); the wire protocol may have changed. Last body: ${lastResponseText.slice(0, 200)}","messagePattern":"12306 rejected every known query endpoint name \\((.+?)\\); the wire protocol may have changed\\. Last body: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/12306/trains.js","lineNumber":100,"sourceCode":"        const text = await resp.text();\n        lastResponseText = text;\n        let json;\n        try { json = JSON.parse(text); } catch {\n            throw new CommandExecutionError(`12306 ${endpoint} returned non-JSON body`);\n        }\n        if (json?.c_url && typeof json.c_url === 'string') {\n            const rotated = await parseRotationEndpoint(resp, endpoint, text);\n            if (rotated && !tried.has(rotated)) {\n                queue.unshift(rotated);\n            }\n            continue;\n        }\n        if (Array.isArray(json?.data?.result)) {\n            return json.data.result;\n        }\n        throw new CommandExecutionError(`12306 ${endpoint} returned an unexpected payload shape`);\n    }\n    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)}`);\n}\n\ncli({\n    site: '12306',\n    name: 'trains',\n    access: 'read',\n    description: 'List trains between two 12306 stations on a given date (anonymous, no login required)',\n    domain: 'kyfw.12306.cn',\n    strategy: Strategy.PUBLIC,\n    browser: false,\n    args: [\n        { name: 'from', positional: true, required: true, help: 'Origin station: Chinese name (北京), telecode (BJP), or pinyin (beijing)' },\n        { name: 'to', positional: true, required: true, help: 'Destination station: same forms as <from>' },\n        { name: 'date', required: true, help: 'Departure date in YYYY-MM-DD' },\n        { name: 'limit', type: 'int', default: 50, help: `Maximum rows (1-${MAX_LIMIT})` },\n    ],\n    columns: [\n        'code', 'from_station', 'to_station', 'start_time', 'arrive_time',","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/12306/trains.js#L82-L118","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nconst QUERY_ENDPOINTS = ['queryG', 'queryO', 'queryZ', 'queryA'];\n// after\nconst QUERY_ENDPOINTS = ['queryG', 'queryO', 'queryZ', 'queryA', 'queryE', 'query']; // add newly rotated names discovered from the live site","handlingStrategy":"fallback","validationCode":"// Probe endpoints before committing to a query run:\nfor (const ep of ['queryG', 'queryO', 'queryZ', 'queryA']) {\n  const r = await fetch(`https://kyfw.12306.cn/otn/leftTicket/${ep}?${probe}`);\n  if (r.ok) console.log('usable endpoint:', ep);\n}","typeGuard":"function isUsableEndpointResponse(json) {\n  return typeof json === 'object' && json !== null &&\n    (typeof json.c_url === 'string' || Array.isArray(json?.data?.result));\n}","tryCatchPattern":"try {\n  rows = await queryLeftTickets(cookie, from, to, date);\n} catch (e) {\n  if (/rejected every known query endpoint/.test(e.message)) {\n    console.error('All endpoints exhausted. Body hint:', e.message.match(/Last body: (.*)$/)?.[1]);\n    // check https://kyfw.12306.cn booking page for the new queryX name and patch QUERY_ENDPOINTS\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["network","endpoint-rotation","api-change","wire-protocol"],"backgroundTag":"endpoint-rotation-exhausted","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}