{"record":{"id":"bc9f264345feed8f","repo":"jackwener/OpenCLI","slug":"from-and-to-must-differ-got-fromname","errorCode":null,"errorMessage":"--from and --to must differ (got ${fromName})","messagePattern":"--from and --to must differ \\(got (.+?)\\)","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/ctrip/train.js","lineNumber":48,"sourceCode":"    args: [\n        { name: 'from', required: true, positional: true, help: 'Departure station or city name (e.g. 北京 / 上海虹桥)' },\n        { name: 'to', required: true, positional: true, help: 'Arrival station or city name (e.g. 上海 / 杭州东)' },\n        { name: 'date', required: true, help: 'Departure date (YYYY-MM-DD)' },\n        { name: 'limit', default: 20, help: 'Number of trains (1-50)' },\n    ],\n    columns: [\n        'rank',\n        'trainNo',\n        'departureTime', 'departureStation',\n        'arrivalTime', 'arrivalStation',\n        'duration', 'fromPrice', 'seats',\n        'url',\n    ],\n    func: async (page, kwargs) => {\n        const fromName = parsePlaceName('from', kwargs.from);\n        const toName = parsePlaceName('to', kwargs.to);\n        if (fromName === toName) {\n            throw new ArgumentError(`--from and --to must differ (got ${fromName})`);\n        }\n        const date = parseIsoDate('date', kwargs.date);\n        const limit = parseListLimit(kwargs.limit);\n\n        const searchUrl = buildTrainListUrl(fromName, toName, date);\n        await page.goto(searchUrl);\n        const waitResult = await page.evaluate(WAIT_FOR_TRAINS_JS);\n        if (waitResult === 'captcha') {\n            throw new AuthRequiredError('trains.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');\n        }\n        if (waitResult !== 'content') {\n            throw new CommandExecutionError(`Ctrip train page did not render train cards (state=${String(waitResult)})`);\n        }\n        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.card-white.list-item', limit));\n        const raw = await page.evaluate(buildTrainExtractJs());\n        if (!Array.isArray(raw)) {\n            throw new CommandExecutionError('Ctrip train DOM extraction returned malformed rows');\n        }","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ctrip/train.js#L30-L66","documentation":"Thrown by the ctrip train list command when the --from and --to values resolve to the same place name. The scraper needs an origin/destination pair to build a Ctrip train search URL; identical endpoints produce an invalid search. It is an ArgumentError because it is a caller input mistake, not a runtime failure.","triggerScenarios":"Calling the train list command with --from and --to that normalize to the same string via parsePlaceName, e.g. --from '上海' --to '上海' or --from 'Shanghai' --to 'shanghai'.","commonSituations":"Copy-pasting the same station into both flags; scripting a loop where from/to variables are accidentally assigned the same value; case or whitespace differences that resolve to the same canonical name.","solutions":["Pass distinct origin and destination values for --from and --to.","Check how parsePlaceName normalizes input (trims, case, aliases) to understand why two different-looking strings collided.","Add a pre-flight check in your script comparing normalized from/to before invoking the command."],"exampleFix":"// before\nawait runTrainList({ from: '上海', to: '上海', date: '2026-09-01' });\n// after\nawait runTrainList({ from: '上海', to: '北京', date: '2026-09-01' });","handlingStrategy":"validation","validationCode":"function norm(s){return String(s).trim().toLowerCase();}\nif (norm(opts.from) === norm(opts.to)) throw new Error('--from and --to must differ');","typeGuard":"const isDistinctPair = (from, to) => norm(from) !== norm(to);","tryCatchPattern":"try { await runTrainList(opts); } catch (e) { if (e instanceof ArgumentError && /must differ/.test(e.message)) { console.error('Fix from/to flags'); } else throw e; }","preventionTips":["Always pass distinct origin/destination values","Compare normalized from/to in wrapper scripts before invoking","Check parsePlaceName normalization rules when using aliases"],"tags":["argument-validation","cli","input-error"],"backgroundTag":"invalid-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}