jackwener/OpenCLI · warning · AuthRequiredError

flights.ctrip.com

Error message

flights.ctrip.com

What it means

An AuthRequiredError raised when the flight results page signals a captcha gate: the in-page WAIT_FOR_BATCH_CAPTURE_JS probe detected location.pathname containing 'captcha' or captcha/human-verification text (验证码, verify the human, 安全验证) in the document body instead of flight cards. The domain ('flights.ctrip.com') is the first argument and is what surfaces as the error message. It tells the agent/operator a human-in-the-loop session is needed before scraping can continue.

Source

Thrown at clis/ctrip/flight.js:192

        const date = parseIsoDate('date', kwargs.date);
        const limit = parseFlightLimit(kwargs.limit);

        const searchUrl =
            `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,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open flights.ctrip.com in the linked human browser session and complete the captcha, then retry the command.
  2. Reduce query frequency and add jitter/delays between searches.
  3. Use a residential IP or your normal browser profile (with existing cookies) instead of a fresh automated profile.
  4. Log in to a Ctrip account in the browser session so risk control trusts the traffic.

Example fix

// before: tight loop from anonymous profile triggers captcha
for (const date of dates) await searchFlights({ from, to, date });

// after: reuse a logged-in session and throttle
await browserLogin('ctrip');
for (const date of dates) {
  await searchFlights({ from, to, date });
  await sleep(5000 + Math.random() * 5000);
}
Defensive patterns

Strategy: retry

Try / catch

try {
  const rows = await ctripFlight({ from, to, date });
} catch (e) {
  if (e instanceof AuthRequiredError && e.message.includes('flights.ctrip.com')) {
    // surface to a human: open the browser session and complete the captcha,
    // then retry with backoff
    await notifyHuman('Complete the Ctrip captcha, then press enter');
    return retryWithBackoff(() => ctripFlight({ from, to, date }), { max: 3 });
  }
  throw e;
}

Prevention

When it happens

Trigger: Opening https://flights.ctrip.com/online/list/oneway-<from>-<to>?depdate=... when Ctrip's risk control redirects the page to /captcha or renders a verification overlay; typically after repeated automated queries, from a datacenter IP, or with a cold/anonymous browser profile lacking trusted cookies.

Common situations: High-frequency polling in CI from cloud IPs; running without the user's logged-in browser session (Strategy relies on session state); shared VPN/proxy IP flagged by Ctrip; sudden risk-control tightening on flights.ctrip.com.

Related errors


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