jackwener/OpenCLI · error · AuthRequiredError

Ctrip is asking for a captcha; complete it in your browser s

Error message

Ctrip is asking for a captcha; complete it in your browser session and retry

What it means

After navigating to the bus list URL, the CLI runs WAIT_FOR_BUS_JS in the page and inspects the returned state. If Ctrip's anti-bot layer serves a captcha instead of content, the CLI throws AuthRequiredError for bus.ctrip.com so the user can solve the captcha in their own authenticated browser session and retry.

Source

Thrown at clis/ctrip/bus.js:58

        'departureTime',
        'fromStation', 'toStation',
        'duration', 'price', 'status',
        'url',
    ],
    func: async (page, kwargs) => {
        const fromCity = parsePlaceName('from', kwargs.from);
        const toCity = parsePlaceName('to', kwargs.to);
        if (fromCity === toCity) {
            throw new ArgumentError(`--from and --to must differ (got ${fromCity})`);
        }
        const date = parseIsoDate('date', kwargs.date);
        const limit = parseListLimit(kwargs.limit);

        const searchUrl = buildBusListUrl(fromCity, toCity, date);
        await page.goto(searchUrl);
        const waitResult = await page.evaluate(WAIT_FOR_BUS_JS);
        if (waitResult === 'captcha') {
            throw new AuthRequiredError('bus.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Ctrip bus page did not render schedule rows (state=${String(waitResult)})`);
        }
        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));
        const raw = await page.evaluate(buildBusExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Ctrip bus DOM extraction returned malformed rows');
        }
        if (raw.length === 0) {
            if (Number(renderedCardCount) > 0) {
                throw new CommandExecutionError('Ctrip bus rows rendered but parser did not find required schedule anchors');
            }
            throw new EmptyResultError('ctrip bus', `No coaches for ${fromCity} to ${toCity} on ${date}`);
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            departureTime: r.departureTime,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open the same browser session manually, solve the captcha on bus.ctrip.com, then rerun the command
  2. Reuse a persistent browser profile with valid Ctrip cookies instead of a fresh/headless one
  3. Slow down request rate and add delays between Ctrip queries
  4. Switch network/IP if your address is being flagged

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  rows = await ctripBusList({ from, to, date });
} catch (e) {
  if (e instanceof AuthRequiredError && e.message.includes('captcha')) {
    // Surface to the operator: manual captcha completion required, then retry once
    await promptManualCaptchaResolution('bus.ctrip.com');
    rows = await ctripBusList({ from, to, date });
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: `clis ctrip bus` navigating to buildBusListUrl(...) and page.evaluate(WAIT_FOR_BUS_JS) returning the literal string 'captcha' — Ctrip flagged the session as automated or the shared browser profile is rate-limited.

Common situations: Running many bus queries in a short window (rate limiting); headless or fresh browser profile with no Ctrip cookies; datacenter IP flagged by Ctrip's WAF; Ctrip tightening anti-bot rules.

Related errors


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