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

The cruise command navigates to the Ctrip cruise index and waits via WAIT_FOR_CRUISE_JS. If the returned state is 'captcha', Ctrip's anti-bot protection is intercepting cruise.ctrip.com, and the CLI throws AuthRequiredError telling the user to solve the captcha in their own browser session and retry.

Source

Thrown at clis/ctrip/cruise.js:52

        { name: 'port', required: true, positional: true, help: 'Departure cruise port name (e.g. 上海 / 威尼斯 / 罗马)' },
        { name: 'limit', default: 20, help: 'Number of cruises (1-50)' },
    ],
    columns: [
        'rank',
        'title', 'star',
        'boarding', 'sailingDate',
        'tags', 'price',
        'url',
    ],
    func: async (page, kwargs) => {
        const port = parsePlaceName('port', kwargs.port);
        const limit = parseListLimit(kwargs.limit);

        const indexUrl = buildCruiseSearchUrl(PORT_INDEX_CODE);
        await page.goto(indexUrl);
        const indexWait = await page.evaluate(WAIT_FOR_CRUISE_JS);
        if (indexWait === 'captcha') {
            throw new AuthRequiredError('cruise.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
        }
        if (indexWait !== 'content') {
            throw new CommandExecutionError(`Ctrip cruise page did not render (state=${String(indexWait)})`);
        }
        const portCode = await page.evaluate(buildCruisePortLookupJs(port));
        if (!portCode) {
            throw new EmptyResultError('ctrip cruise', `No cruise departure port matching "${port}" (try a listed sea-cruise port like 上海 / 威尼斯 / 罗马)`);
        }

        let searchUrl = indexUrl;
        if (portCode !== PORT_INDEX_CODE) {
            searchUrl = buildCruiseSearchUrl(portCode);
            await page.goto(searchUrl);
            const portWait = await page.evaluate(WAIT_FOR_CRUISE_JS);
            if (portWait === 'captcha') {
                throw new AuthRequiredError('cruise.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
            }
            if (portWait === 'empty') {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open the same browser session, complete the captcha on cruise.ctrip.com, then rerun
  2. Use a persistent browser profile with established Ctrip cookies
  3. Throttle query frequency and add jitter/delays between runs
  4. Try a different network/IP if flagged persistently

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  cruises = await ctripCruiseList({ port });
} catch (e) {
  if (e instanceof AuthRequiredError && e.message.includes('captcha')) {
    await promptManualCaptchaResolution('cruise.ctrip.com');
    cruises = await ctripCruiseList({ port });
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: `clis ctrip cruise` where page.evaluate(WAIT_FOR_CRUISE_JS) on buildCruiseSearchUrl(PORT_INDEX_CODE) returns 'captcha' — the session or IP was flagged by Ctrip's WAF.

Common situations: High-frequency cruise queries triggering rate limits; headless/automation-flagged browser; shared datacenter IP with poor reputation; expired session cookies prompting re-verification.

Related errors


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