jackwener/OpenCLI · error · ArgumentError

<from> and <to> must differ; both resolved to ${fromStation.

Error message

<from> and <to> must differ; both resolved to ${fromStation.name} (${fromStation.code})

What it means

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.

Source

Thrown at clis/12306/trains.js:134

    ],
    columns: [
        'code', 'from_station', 'to_station', 'start_time', 'arrive_time',
        'duration', 'available', 'business_seat', 'first_seat', 'second_seat',
        'soft_sleeper', 'hard_sleeper', 'hard_seat', 'no_seat', 'train_no',
    ],
    func: async (kwargs) => {
        const fromArg = String(kwargs.from ?? '').trim();
        const toArg = String(kwargs.to ?? '').trim();
        if (!fromArg) throw new ArgumentError('<from> station must not be empty');
        if (!toArg) throw new ArgumentError('<to> station must not be empty');
        const date = validateDate(kwargs.date);
        const limit = normalizeLimit(kwargs.limit, 50, MAX_LIMIT);

        const stations = await fetchStationBundle();
        const fromStation = resolveStation(stations, fromArg);
        const toStation = resolveStation(stations, toArg);
        if (fromStation.code === toStation.code) {
            throw new ArgumentError(`<from> and <to> must differ; both resolved to ${fromStation.name} (${fromStation.code})`);
        }
        const stationByCode = new Map(stations.map((s) => [s.code, s]));

        const cookieHeader = await mintSession();
        const rawRows = await queryLeftTickets(cookieHeader, fromStation.code, toStation.code, date);
        const decoded = rawRows
            .map((line) => parseTrainRecord(decodeURIComponent(line.replace(/%0A/g, '')), stationByCode))
            .filter(Boolean);

        if (decoded.length === 0) {
            throw new EmptyResultError(
                `No trains found from ${fromStation.name} to ${toStation.name} on ${date}`,
                'Try a different date or check whether the route is operated by 12306.',
            );
        }
        return decoded.slice(0, limit);
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Supply genuinely different origin and destination stations.
  2. If you intended a different station in the same city, use the specific station name (e.g. 北京南 instead of 北京).
  3. Check resolved stations before calling: run the stations command or resolveStation to confirm the two inputs map to different telecodes.
  4. Fix scripts where from/to come from the same config variable.

Example fix

// before
await trains({ from: '北京', to: 'beijing', date: '2026-09-01' }); // same station
// after
await trains({ from: '北京', to: '上海虹桥', date: '2026-09-01' });
Defensive patterns

Strategy: validation

Validate before calling

// Resolve first, compare telecodes, then call:
const stations = await fetchStationBundle();
const a = resolveStation(stations, from);
const b = resolveStation(stations, to);
if (a.code === b.code) throw new Error(`from/to both resolve to ${a.name} (${a.code})`);

Type guard

function areDistinctStations(x, y) {
  return x.code !== y.code;
}

Try / catch

try {
  await trains({ from, to, date });
} catch (e) {
  if (e.name === 'ArgumentError' && /must differ; both resolved to/.test(e.message)) {
    console.error('Origin and destination are the same station — pick two different stations.');
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/32d1b5b3cc381599. Report an issue: GitHub.