{"record":{"id":"495f66d4653e960b","repo":"jackwener/OpenCLI","slug":"12306-querybytrainno-returned-http-resp-status-495f66","errorCode":null,"errorMessage":"12306 queryByTrainNo returned HTTP ${resp.status}","messagePattern":"12306 queryByTrainNo returned HTTP (.+?)","errorType":"http","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/12306/train.js","lineNumber":25,"sourceCode":" */\nimport { cli, Strategy } from '@jackwener/opencli/registry';\nimport { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';\nimport { fetchStationBundle, mintSession, resolveStation, validateDate } from './utils.js';\n\nconst UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0 Safari/537.36';\nconst TRAIN_NO_RE = /^[0-9A-Za-z]{8,18}$/;\n\nasync function queryStops(cookieHeader, trainNo, fromCode, toCode, date, fetchImpl = fetch) {\n    const url = `https://kyfw.12306.cn/otn/czxx/queryByTrainNo?train_no=${trainNo}&from_station_telecode=${fromCode}&to_station_telecode=${toCode}&depart_date=${date}`;\n    const resp = await fetchImpl(url, {\n        headers: {\n            'User-Agent': UA,\n            'Referer': 'https://kyfw.12306.cn/otn/leftTicket/init',\n            'Cookie': cookieHeader,\n        },\n    });\n    if (!resp.ok) {\n        throw new CommandExecutionError(`12306 queryByTrainNo returned HTTP ${resp.status}`);\n    }\n    let json;\n    try {\n        json = await resp.json();\n    } catch {\n        throw new CommandExecutionError('12306 queryByTrainNo returned non-JSON body');\n    }\n    if (json?.status !== true || !Array.isArray(json?.data?.data)) {\n        throw new CommandExecutionError(`12306 queryByTrainNo returned an unexpected payload shape`);\n    }\n    return json.data.data;\n}\n\ncli({\n    site: '12306',\n    name: 'train',\n    access: 'read',\n    description: 'List every station a 12306 train calls at, with arrival / departure / stopover time (anonymous, no login required)',","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/12306/train.js#L7-L43","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Retry after a delay with backoff — transient 5xx/anti-bot blocks usually clear within seconds to minutes.","Re-mint the session (the CLI does this per command) and retry once; a stale cookie is a common cause of 302/403.","Reduce query frequency / add jitter between calls to avoid rate-limit and WAF blocks.","Check the status code in the message: 403/302 points to anti-bot or cookie issues, 5xx points to 12306 server problems.","Verify network path (proxy/VPN) isn't intercepting kyfw.12306.cn."],"exampleFix":"// before (tight loop, gets blocked)\nfor (const t of trains) await stops(t);\n// after (backoff + retry)\nfor (const t of trains) {\n  try { await stops(t); }\n  catch (e) { if (/HTTP (302|403|5\\d\\d)/.test(e.message)) { await sleep(2000); await stops(t); } }\n  await sleep(500);\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"const withRetry = async (fn, tries = 3) => {\n  for (let i = 0; i < tries; i++) {\n    try { return await fn(); }\n    catch (e) {\n      const m = /queryByTrainNo returned HTTP (\\d+)/.exec(e.message);\n      if (!m || !/^(302|403|429|5\\d\\d)$/.test(m[1]) || i === tries - 1) throw e;\n      await new Promise((r) => setTimeout(r, 1000 * 2 ** i));\n    }\n  }\n};\nconst stopsData = await withRetry(() => stops(trainNo));","preventionTips":["Space out queries (hundreds of ms + jitter) to avoid 12306 anti-bot/HTTP blocks.","Retry with exponential backoff on 5xx and 403/429.","Re-mint session cookies when you see 302/403 statuses.","Surface the HTTP status from the message to decide retry vs abort."],"tags":["network","http","upstream-api","rate-limiting"],"backgroundTag":"http-error-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}