jackwener/OpenCLI · error · TimeoutError

Ctrip flight API capture

Error message

Ctrip flight API capture

What it means

A TimeoutError raised when no batchSearch network response was captured within CAPTURE_TIMEOUT_SECONDS (12s) after opening the results page. parseBatchSearchCaptures returns null when the capture log contains no entry whose URL includes '/international/search/api/search/batchSearch', and the command converts that into this timeout so callers know the structured API response never arrived.

Source

Thrown at clis/ctrip/flight.js:196

            `https://flights.ctrip.com/online/list/oneway-${fromCode.toLowerCase()}-${toCode.toLowerCase()}` +
            `?depdate=${date}&cabin=Y_S_C_F&adult=1&child=0&infant=0`;
        if (typeof page?.startNetworkCapture !== 'function' ||
            typeof page?.readNetworkCapture !== 'function' ||
            !await page.startNetworkCapture(CAPTURE_PATTERN)) {
            throw new CommandExecutionError('Ctrip flight requires browser response interception');
        }
        await page.readNetworkCapture();
        await page.goto(searchUrl);
        // The initial document can finish before the large batchSearch body.
        // The first rendered card is only a readiness signal; row data still
        // comes exclusively from the structured response below.
        const readiness = await page.evaluate(WAIT_FOR_BATCH_CAPTURE_JS);
        if (readiness === 'captcha') {
            throw new AuthRequiredError('flights.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
        }
        const itineraries = parseBatchSearchCaptures(await page.readNetworkCapture());
        if (!itineraries) {
            throw new TimeoutError('Ctrip flight API capture', CAPTURE_TIMEOUT_SECONDS, 'No batchSearch response was observed after opening the results page.');
        }
        if (itineraries.length === 0) {
            throw new EmptyResultError('ctrip flight', `No flights for ${fromCode}→${toCode} on ${date}`);
        }
        const rows = itineraries
            .map((itinerary, index) => mapItinerary(itinerary, searchUrl, index))
            // The page groups direct flights before transfers, then applies its
            // displayed starting price and departure-time order inside each group.
            .sort(([rowA, connectingA, departureA, idA], [rowB, connectingB, departureB, idB]) =>
                Number(connectingA) - Number(connectingB) || rowA.price - rowB.price ||
                departureA.localeCompare(departureB) || idA.localeCompare(idB))
            .slice(0, limit)
            .map(([row], index) => ({
                rank: index + 1,
                airline: row.airline,
                flightNo: row.flightNo,
                aircraft: row.aircraft,
                departureTime: row.departureTime,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry — transient slowness or a stuck page is the most common cause.
  2. Increase CAPTURE_TIMEOUT_SECONDS if your network routinely takes >12s for the large batchSearch payload.
  3. Verify the capture pattern still matches by checking the Network tab for the search XHR; update CAPTURE_PATTERN if Ctrip changed the endpoint.
  4. Confirm the page isn't being soft-blocked (no flight cards rendered either) and, if so, treat it as an anti-bot issue: use a trusted session/IP.

Example fix

// before
const CAPTURE_TIMEOUT_SECONDS = 12;

// after: tolerate slow networks
const CAPTURE_TIMEOUT_SECONDS = 30;
Defensive patterns

Strategy: retry

Try / catch

try {
  const rows = await ctripFlight({ from, to, date });
} catch (e) {
  if (e instanceof TimeoutError && e.message.includes('Ctrip flight API capture')) {
    return retryWithBackoff(() => ctripFlight({ from, to, date }), { attempts: 2, baseMs: 3000 });
  }
  throw e;
}

Prevention

When it happens

Trigger: Ctrip never issues the batchSearch XHR (page stuck on a shell, JS errors, or the request URL pattern changed); the response arrives after the 12s window on a slow connection; startNetworkCapture missed the request because interception began late; the page navigated to a captcha/verify state without setting the 'captcha' readiness signal.

Common situations: Slow or throttled networks; regional Ctrip variants served under different API paths; heavy load days where Ctrip delays search; an A/B test replacing batchSearch with a new endpoint; anti-bot soft-block returning no data without an explicit captcha page.

Related errors


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