{"record":{"id":"90c6ae6092d0b57a","repo":"jackwener/OpenCLI","slug":"from-and-to-must-differ-both-resolved-to-fr","errorCode":null,"errorMessage":"--from and --to must differ; both resolved to ${fromStation.name} (${fromStation.code})","messagePattern":"--from and --to must differ; both resolved to (.+?) \\((.+?)\\)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/12306/price.js","lineNumber":148,"sourceCode":"                `<train-no> \"${trainNo}\" does not look like a 12306 internal train_no`,\n                'Use the train_no field from `12306 trains` output (e.g. 24000000G10L), not the public code (G1).',\n            );\n        }\n        const fromArg = String(kwargs.from ?? '').trim();\n        const toArg = String(kwargs.to ?? '').trim();\n        if (!fromArg) throw new ArgumentError('--from station must not be empty');\n        if (!toArg) throw new ArgumentError('--to station must not be empty');\n        const date = validateDate(kwargs.date);\n        const seatTypes = String(kwargs['seat-types'] ?? '').trim() || 'OM9PA1A3A4FWZ';\n        if (!SEAT_TYPES_RE.test(seatTypes)) {\n            throw new ArgumentError('--seat-types must contain only 12306 seat letters/digits (A-Z, 0-9)');\n        }\n\n        const stations = await fetchStationBundle();\n        const fromStation = resolveStation(stations, fromArg);\n        const toStation = resolveStation(stations, toArg);\n        if (fromStation.code === toStation.code) {\n            throw new ArgumentError(`--from and --to must differ; both resolved to ${fromStation.name} (${fromStation.code})`);\n        }\n\n        const cookieHeader = await mintSession();\n        const stops = await queryStopsForPrice(cookieHeader, trainNo, fromStation.code, toStation.code, date);\n        const { fromNo, toNo } = pickStationNos(stops, fromStation.code, toStation.code, fromStation.name, toStation.name);\n        const priceData = await queryPrice(cookieHeader, trainNo, fromNo, toNo, seatTypes, date);\n        const rows = parsePriceData(priceData);\n        if (rows.length === 0) {\n            throw new EmptyResultError(\n                `No prices returned for train_no=${trainNo} ${fromStation.name} -> ${toStation.name} on ${date}`,\n                'Try a different seat-types letter set, or check that this train operates on the date.',\n            );\n        }\n        return rows;\n    },\n});\n\nexport const __test__ = { parsePriceData, pickStationNos, queryStopsForPrice, queryPrice, SEAT_LETTERS, TRAIN_NO_RE };","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/12306/price.js#L130-L166","documentation":"After resolving both --from and --to through the station bundle, the library throws this ArgumentError when both arguments resolve to the same station telecode — a price query for an identical origin/destination is meaningless and 12306 would reject it. The message includes the resolved station name and code to reveal which input matched (e.g. aliases of the same station).","triggerScenarios":"Passing the same station twice (`--from 上海 --to 上海`), or two aliases of one station (e.g. `--from 北京 --to 北京南` when both resolve to the same telecode, or pinyin vs Chinese name of the same station).","commonSituations":"Template variables both defaulting to the same city; users not realizing pinyin/telecode/Chinese name forms can resolve to the same station; copy-paste duplication in generated commands.","solutions":["Choose genuinely different origin and destination stations for the segment.","Check the resolved name/code in the message; if an alias collided, use the more specific station name or telecode.","Validate from !== to (after resolution) in scripts before invoking the command."],"exampleFix":"// before\n12306 price --train-no 24000000G10L --from 上海 --to 上海\n// after\n12306 price --train-no 24000000G10L --from 上海 --to 南京","handlingStrategy":"validation","validationCode":"if (String(opts.from).trim() === String(opts.to).trim()) {\n  throw new Error('--from and --to must be different stations');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await price({ 'train-no': tn, from, to, date });\n} catch (e) {\n  if (/both resolved to/.test(e.message)) {\n    const [name, code] = e.message.match(/resolved to (.+?) \\((.+?)\\)/)?.slice(1) ?? [];\n    console.error(`${from} and ${to} both resolve to ${name} (${code}); pick a different endpoint.`);\n  } else throw e;\n}","preventionTips":["Never pass the same station (or two aliases of it) as --from and --to.","Resolve both arguments via `12306 stations` first and compare the returned `code` fields.","When templating, assert the two station variables differ before invocation."],"tags":["argument-validation","cli","station-resolution"],"backgroundTag":"invalid-argument-combination","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}