jackwener/OpenCLI · error · ArgumentError

<train-no> must not be empty

Error message

<train-no> must not be empty

What it means

The `12306 price` command throws this ArgumentError during argument validation when the positional <train-no> argument is missing or resolves to an empty/whitespace-only string. It fails fast before any network calls because the internal train_no is required for every downstream API call.

Source

Thrown at clis/12306/price.js:127

cli({
    site: '12306',
    name: 'price',
    access: 'read',
    description: 'Look up 12306 ticket prices by seat class for one train on a given date and segment (anonymous, no login required)',
    domain: 'kyfw.12306.cn',
    strategy: Strategy.PUBLIC,
    browser: false,
    args: [
        { name: 'train-no', positional: true, required: true, help: 'Internal train_no from `12306 trains` (e.g. 24000000G10L)' },
        { name: 'from', required: true, help: 'Origin station (Chinese name, telecode, or pinyin) - must be a stop of this train' },
        { name: 'to', required: true, help: 'Destination station - must be a stop of this train' },
        { name: 'date', required: true, help: 'Departure date in YYYY-MM-DD' },
        { name: 'seat-types', default: 'OM9PA1A3A4FWZ', help: 'Seat-type letters to query (default covers the common classes). Examples: OM9 (二等/一等/商务), A1A3A4 (硬座/硬卧/软卧).' },
    ],
    columns: ['seat_code', 'seat_name', 'price', 'currency'],
    func: async (kwargs) => {
        const trainNo = String(kwargs['train-no'] ?? '').trim();
        if (!trainNo) throw new ArgumentError('<train-no> must not be empty');
        if (!TRAIN_NO_RE.test(trainNo)) {
            throw new ArgumentError(
                `<train-no> "${trainNo}" does not look like a 12306 internal train_no`,
                'Use the train_no field from `12306 trains` output (e.g. 24000000G10L), not the public code (G1).',
            );
        }
        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 seatTypes = String(kwargs['seat-types'] ?? '').trim() || 'OM9PA1A3A4FWZ';
        if (!SEAT_TYPES_RE.test(seatTypes)) {
            throw new ArgumentError('--seat-types must contain only 12306 seat letters/digits (A-Z, 0-9)');
        }

        const stations = await fetchStationBundle();
        const fromStation = resolveStation(stations, fromArg);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pass the internal train_no positional argument, e.g. `12306 price 24000000G10L --from 北京南 --to 上海虹桥 --date 2026-09-01`.
  2. Get the value from the train_no column of `12306 trains` output, not the public code (G1).
  3. Check that the variable feeding the argument is non-empty in your script (guard before invoking).
  4. Ensure proper quoting so the shell does not drop the token.
  5. Distinguish this empty-argument error from the companion format error (non-empty but not matching TRAIN_NO_RE) — fix whichever validation you hit.

Example fix

// before
const trainNo = rows[0].public_code; // e.g. "G1", or empty if row missing
await priceCmd({ 'train-no': trainNo, from: '北京南', to: '上海虹桥', date });
// after
const trainNo = rows[0]?.train_no ?? '';
if (!trainNo.trim()) throw new Error('run `12306 trains` first; train_no is required');
await priceCmd({ 'train-no': trainNo, from: '北京南', to: '上海虹桥', date });
Defensive patterns

Strategy: validation

Validate before calling

const trainNo = String(kwargs['train-no'] ?? '').trim();
if (!trainNo) throw new Error('12306 price requires a positional <train-no>; get it from `12306 trains` (train_no column)');
if (!/^[0-9A-Za-z]{8,18}$/.test(trainNo)) throw new Error("'" + trainNo + "' is not an internal train_no (public codes like G1 are rejected)");

Type guard

function isValidTrainNo(v) {
  return typeof v === 'string' && /^[0-9A-Za-z]{8,18}$/.test(v.trim());
}

Try / catch

try {
  rows = await run12306Price({ 'train-no': trainNo, from, to, date });
} catch (err) {
  if (err instanceof ArgumentError && /must not be empty/.test(err.message)) {
    console.error('Usage: 12306 price <train-no> --from <station> --to <station> --date YYYY-MM-DD');
    process.exitCode = 2;
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling `12306 price` with no positional argument (`12306 price --from X --to Y --date D`), passing an empty string, or a value that is only whitespace; programmatically passing undefined/null kwargs['train-no'].

Common situations: Script-built command lines where a variable is empty because an upstream `12306 trains` lookup failed; shell quoting mistakes dropping the argument; copying only the public code after deleting the train_no field; template expansion leaving the positional blank.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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