jackwener/OpenCLI · warning · EmptyResultError

No trains found from ${fromStation.name} to ${toStation.name

Error message

No trains found from ${fromStation.name} to ${toStation.name} on ${date}

What it means

EmptyResultError thrown when the 12306 query succeeded but, after decoding and parsing every returned train row, zero valid records remained. This distinguishes a genuinely empty result (no trains for that route/date) from a protocol error, and suggests trying a different date or verifying the route exists.

Source

Thrown at clis/12306/trains.js:145

        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);
    },
});

export const __test__ = { extractQueryEndpoint, queryLeftTickets };

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Pick a date within the 12306 pre-sale window (typically 15 days ahead) and in the future.
  2. Verify the route has direct service — try the major stations of each city (e.g. 北京 to 上海) to confirm the corridor has trains.
  3. Try adjacent dates; some routes run only on certain weekdays or seasons.
  4. If you expected trains but get none consistently, check whether parseTrainRecord is silently dropping rows (12306 may have changed the record field count); log rawRows length vs decoded length.
Defensive patterns

Strategy: try-catch

Validate before calling

const days = (Date.parse(date + 'T00:00:00Z') - Date.now()) / 86400000;
if (days < 0 || days > 15) {
  console.warn(`date ${date} is outside the 12306 pre-sale window (0-15 days ahead); expect no trains`);
}

Type guard

function isWithinPresaleWindow(dateStr) {
  const t = Date.parse(dateStr + 'T00:00:00Z');
  if (Number.isNaN(t)) return false;
  const days = (t - Date.now()) / 86400000;
  return days >= 0 && days <= 15;
}

Try / catch

try {
  rows = await trains({ from, to, date });
} catch (e) {
  if (e.name === 'EmptyResultError' && /No trains found/.test(e.message)) {
    console.warn(e.message, '-', e.hint || 'try the city main stations or an adjacent date');
    return []; // handle empty gracefully instead of crashing
  }
  throw e;
}

Prevention

When it happens

Trigger: A query for a route/date where 12306 returns no train rows (or rows that all fail parseTrainRecord's `f.length < 33` check, so filter(Boolean) drops them): a date beyond the 15-day booking window, a route with no direct trains, a past date, or a schedule change leaving the response empty.

Common situations: Booking too far in advance (outside the pre-sale period); querying weekend-only or seasonal routes on non-operating days; querying a past date; typo'd but valid stations with no direct connection (e.g. small stations requiring transfer); 12306 schedule updates removing a service.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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