jackwener/OpenCLI · error · ArgumentError
<train-no> "${trainNo}" does not look like a 12306 internal
Error message
<train-no> "${trainNo}" does not look like a 12306 internal train_no What it means
The `12306 price` command requires the internal 12306 `train_no` (e.g. 24000000G10L), an 8-18 char alphanumeric identifier returned as the `train_no` field of `12306 trains`, not the public train code like G1. The library throws this ArgumentError when the value passed to `--train-no` fails the TRAIN_NO_RE pattern (/^[0-9A-Za-z]{8,18}$/). It exists to fail fast before making network calls with an identifier the upstream API would reject.
Source
Thrown at clis/12306/price.js:129
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);
const toStation = resolveStation(stations, toArg);
if (fromStation.code === toStation.code) {View on GitHub (pinned to 49907e53dc)
Solutions
- Run `12306 trains` first and copy the `train_no` field value (e.g. 24000000G10L), not the public code.
- Check the value is 8-18 alphanumeric characters with no whitespace, quotes, or hyphens: /^[0-9A-Za-z]{8,18}$/.
- If scripting, extract train_no programmatically from `12306 trains --json` output rather than hardcoding.
Example fix
// before 12306 price --train-no G1 --from 北京南 --to 上海 // after 12306 trains --from 北京南 --to 上海 # take train_no, e.g. 24000000G10L 12306 price --train-no 24000000G10L --from 北京南 --to 上海
Defensive patterns
Strategy: validation
Validate before calling
const trainNo = String(opts['train-no'] ?? '').trim();
if (!/^[0-9A-Za-z]{8,18}$/.test(trainNo)) {
throw new Error(`--train-no must be the internal train_no from '12306 trains' (8-18 alphanumerics), got: ${trainNo}`);
} Type guard
const isValidTrainNo = (v) => typeof v === 'string' && /^[0-9A-Za-z]{8,18}$/.test(v); Try / catch
try {
const rows = await price({ 'train-no': tn, from, to, date });
} catch (e) {
if (/does not look like a 12306 internal train_no/.test(e.message)) {
const trains = await trainsCmd({ from, to, date });
tn = trains[0].train_no; // recover by looking up the real train_no
} else throw e;
} Prevention
- Always source train_no from the `12306 trains` output, never the public code.
- Validate with /^[0-9A-Za-z]{8,18}$/ before calling.
- In scripts, parse train_no programmatically from `12306 trains --json` instead of hardcoding.
When it happens
Trigger: Calling `12306 price --train-no G1 --from ... --to ...` where train-no is empty after trimming, too short/long (not 8-18 chars), or contains characters outside A-Z a-z 0-9 — typically because the public train code (G1, K599) was passed instead of the internal train_no.
Common situations: Developers copy the public train code from a timetable or ticket instead of the `train_no` column from `12306 trains` output; quoting/whitespace issues that leave stray characters; piping the wrong CSV/JSON field into the CLI.
Related errors
- --seat-types must contain only 12306 seat letters/digits (A-
- coords must be in "x,y" format (e.g. "1,2")
- semanticscholar paper id "${value}" is not recognised
- --${name} must be a numeric Trip.com hotel id, got ${JSON.st
- --from station must not be empty
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/07de5a402a5dfbb4.
Report an issue: GitHub.