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
- Open ship.ctrip.com in the CLI's browser session, complete the captcha, then rerun the command
- Re-export fresh login cookies for ship.ctrip.com and retry
- Slow down / spread out queries and add delays between ferry searches
- 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
- Refresh exported ship.ctrip.com cookies regularly; stale cookies raise risk scores
- Pace ferry queries (seconds between requests) and avoid large batch sweeps
- Run from residential IPs; avoid datacenter/VPN ranges Ctrip flags
- Probe session health once before batch runs and abort early on captcha
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
- Ctrip is asking for a captcha; complete it in your browser s
- Ctrip is asking for a captcha; complete it in your browser s
- Ctrip is asking for a captcha; complete it in your browser s
- Ctrip is asking for a captcha; complete it in your browser s
- Ctrip is asking for a captcha; complete it in your browser s
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/e8407e6dc769cf95.
Report an issue: GitHub.