jackwener/OpenCLI · error · ArgumentError

--from and --to must differ (got ${fromCity})

Error message

--from and --to must differ (got ${fromCity})

What it means

The ctrip bus list command requires a distinct origin and destination city. parsePlaceName normalizes both --from and --to, and if they resolve to the same city name, ArgumentError is thrown because Ctrip cannot search same-city bus routes. This is a pre-flight argument validation, thrown before any page navigation.

Source

Thrown at clis/ctrip/bus.js:49

    navigateBefore: false,
    args: [
        { name: 'from', required: true, positional: true, help: 'Departure city name (e.g. 北京 / 上海)' },
        { name: 'to', required: true, positional: true, help: 'Arrival city name (e.g. 天津 / 杭州)' },
        { name: 'date', required: true, help: 'Departure date (YYYY-MM-DD)' },
        { name: 'limit', default: 20, help: 'Number of departures (1-50)' },
    ],
    columns: [
        'rank',
        'departureTime',
        'fromStation', 'toStation',
        'duration', 'price', 'status',
        'url',
    ],
    func: async (page, kwargs) => {
        const fromCity = parsePlaceName('from', kwargs.from);
        const toCity = parsePlaceName('to', kwargs.to);
        if (fromCity === toCity) {
            throw new ArgumentError(`--from and --to must differ (got ${fromCity})`);
        }
        const date = parseIsoDate('date', kwargs.date);
        const limit = parseListLimit(kwargs.limit);

        const searchUrl = buildBusListUrl(fromCity, toCity, date);
        await page.goto(searchUrl);
        const waitResult = await page.evaluate(WAIT_FOR_BUS_JS);
        if (waitResult === 'captcha') {
            throw new AuthRequiredError('bus.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Ctrip bus page did not render schedule rows (state=${String(waitResult)})`);
        }
        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));
        const raw = await page.evaluate(buildBusExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Ctrip bus DOM extraction returned malformed rows');
        }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass different values for --from and --to
  2. Check how your city names normalize (parsePlaceName) — aliases like 上海市 normalize to the same city as 上海
  3. If you actually want intra-city travel, use a different command or transport type (bus search is intercity)

Example fix

// before
clis ctrip bus --from 上海 --to 上海市
// after
clis ctrip bus --from 上海 --to 杭州
Defensive patterns

Strategy: validation

Validate before calling

const norm = (s) => String(s || '').trim();
if (norm(from) === norm(to)) {
  throw new Error(`--from and --to must differ (got ${norm(from)})`);
}

Type guard

null

Try / catch

try {
  await ctripBusList({ from, to, date });
} catch (e) {
  if (e instanceof ArgumentError && e.message.includes('must differ')) {
    console.error('Invalid route: origin and destination are the same city');
    process.exitCode = 2;
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Invoking the bus command with `--from` and `--to` set to the same city (or values that normalize to the same name, e.g. "上海" vs "上海市").

Common situations: Scripting the CLI with a variable that accidentally defaults to the origin; typos or full/short city names resolving identically; copy-paste leaving both flags the same.

Related errors


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