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

AuthRequiredError thrown when WAIT_FOR_FERRY_JS detects the captcha state on ship.ctrip.com after navigating to the ferry results deep link. The library drives the user's real cookie session (Strategy.COOKIE) and will not bypass captchas automatically, so it raises this error instructing the user to solve the challenge in the browser session and retry. Thrown before any scrolling or extraction, so no sailing data is available.

Source

Thrown at clis/ctrip/ferry.js:58

        'departureTime', 'fromPort',
        'arrivalTime', 'toPort',
        '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 = buildFerryListUrl(fromCity, toCity, date);
        await page.goto(searchUrl);
        const waitResult = await page.evaluate(WAIT_FOR_FERRY_JS);
        if (waitResult === 'captcha') {
            throw new AuthRequiredError('ship.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Ctrip ferry page did not render sailing rows (state=${String(waitResult)})`);
        }
        const renderedCardCount = await page.evaluate(buildScrollUntilJs('.list-item-parent', limit));
        const raw = await page.evaluate(buildFerryExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Ctrip ferry DOM extraction returned malformed rows');
        }
        if (raw.length === 0) {
            if (Number(renderedCardCount) > 0) {
                throw new CommandExecutionError('Ctrip ferry rows rendered but parser did not find required sailing anchors');
            }
            throw new EmptyResultError('ctrip ferry', `No sailings for ${fromCity} to ${toCity} on ${date}`);
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            shipName: r.shipName,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open ship.ctrip.com in the CLI's browser session, complete the captcha, then rerun the command
  2. Re-export fresh login cookies for ship.ctrip.com and retry
  3. Slow down / spread out queries and add delays between ferry searches
  4. Use a residential network or rotate to a trusted IP

Example fix

// before: hammering many dates triggers captcha
for (const d of dates) {
  results.push(await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]));
}
// after: handle captcha and pace requests
for (const d of dates) {
  try {
    results.push(await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]));
  } catch (e) {
    if (e.name === 'AuthRequiredError') {
      await promptCaptchaSolve();
      results.push(await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]));
    } else throw e;
  }
  await sleep(5000);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight probe of ship.ctrip.com session health before running the actual query
const probe = await page.goto('https://ship.ctrip.com/').then(() => page.evaluate(WAIT_FOR_FERRY_JS));
if (probe === 'captcha') console.error('Solve the Ctrip captcha in your browser session before querying ferries');

Type guard

function isAuthRequiredError(e) {
  return e instanceof Error && e.name === 'AuthRequiredError';
}

Try / catch

try {
  return await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]);
} catch (e) {
  if (isAuthRequiredError(e)) {
    await promptUserToSolveCaptcha('ship.ctrip.com');
    return retry(() => run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]), { retries: 1 });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `ctrip ferry <from> <to> --date <d>` where, right after page.goto(buildFerryListUrl(...)), the in-page probe finds Ctrip's anti-bot captcha overlay instead of sailing rows — triggered by expired/stale cookies, suspicious IP, or aggressive query frequency.

Common situations: Batch-querying many ferry routes/dates until Ctrip's risk engine challenges the session; running from datacenter/VPN IPs; cookie export grown stale; shared IP already flagged by other users.

Related errors


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