{"record":{"id":"32d1b5b3cc381599","repo":"jackwener/OpenCLI","slug":"from-and-to-must-differ-both-resolved-to-fr-32d1b5","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/trains.js","lineNumber":134,"sourceCode":"    ],\n    columns: [\n        'code', 'from_station', 'to_station', 'start_time', 'arrive_time',\n        'duration', 'available', 'business_seat', 'first_seat', 'second_seat',\n        'soft_sleeper', 'hard_sleeper', 'hard_seat', 'no_seat', 'train_no',\n    ],\n    func: async (kwargs) => {\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 limit = normalizeLimit(kwargs.limit, 50, MAX_LIMIT);\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        const stationByCode = new Map(stations.map((s) => [s.code, s]));\n\n        const cookieHeader = await mintSession();\n        const rawRows = await queryLeftTickets(cookieHeader, fromStation.code, toStation.code, date);\n        const decoded = rawRows\n            .map((line) => parseTrainRecord(decodeURIComponent(line.replace(/%0A/g, '')), stationByCode))\n            .filter(Boolean);\n\n        if (decoded.length === 0) {\n            throw new EmptyResultError(\n                `No trains found from ${fromStation.name} to ${toStation.name} on ${date}`,\n                'Try a different date or check whether the route is operated by 12306.',\n            );\n        }\n        return decoded.slice(0, limit);\n    },\n});","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/12306/trains.js#L116-L152","documentation":"ArgumentError thrown when `from` and `to` resolve to the same station telecode. 12306 cannot answer a leftTicket query whose origin equals the destination (there are no trains from a station to itself), so the command rejects it after resolution, reporting the resolved station name and telecode.","triggerScenarios":"Passing two identifiers that resolve to one station — e.g. `trains 北京 beijing 2026-09-01` (Chinese name vs pinyin of the same station), `trains BJP BJP ...`, or two different aliases/telecodes that map to the same physical station via the station bundle.","commonSituations":"Testing the CLI with the same city twice; confusion between city name and station name when both resolve to the same main station (e.g. 上海 and 上海虹桥 resolving distinctly, but 北京 and 北京南 do not — while 北京 北京南 actually differ, alias collisions like `bjp` and `北京` do not); a script defaulting both endpoints to the same variable.","solutions":["Supply genuinely different origin and destination stations.","If you intended a different station in the same city, use the specific station name (e.g. 北京南 instead of 北京).","Check resolved stations before calling: run the stations command or resolveStation to confirm the two inputs map to different telecodes.","Fix scripts where from/to come from the same config variable."],"exampleFix":"// before\nawait trains({ from: '北京', to: 'beijing', date: '2026-09-01' }); // same station\n// after\nawait trains({ from: '北京', to: '上海虹桥', date: '2026-09-01' });","handlingStrategy":"validation","validationCode":"// Resolve first, compare telecodes, then call:\nconst stations = await fetchStationBundle();\nconst a = resolveStation(stations, from);\nconst b = resolveStation(stations, to);\nif (a.code === b.code) throw new Error(`from/to both resolve to ${a.name} (${a.code})`);","typeGuard":"function areDistinctStations(x, y) {\n  return x.code !== y.code;\n}","tryCatchPattern":"try {\n  await trains({ from, to, date });\n} catch (e) {\n  if (e.name === 'ArgumentError' && /must differ; both resolved to/.test(e.message)) {\n    console.error('Origin and destination are the same station — pick two different stations.');\n  } else throw e;\n}","preventionTips":["Resolve both inputs to telecodes and compare before querying.","Remember Chinese name, pinyin, and aliases of one station all collide — don't mix forms of the same station.","For same-city trips use specific different stations (e.g. 北京南 → 北京丰台).","In configs, store distinct origin/destination variables and assert inequality."],"tags":["argument-validation","duplicate-argument","station-resolution"],"backgroundTag":"identical-origin-destination","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}