jackwener/OpenCLI · error · CommandExecutionError
Ctrip ferry DOM extraction returned malformed rows
Error message
Ctrip ferry DOM extraction returned malformed rows
What it means
CommandExecutionError thrown when buildFerryExtractJs's page.evaluate resolves to a non-array value — the in-page extraction script broke its contract of returning an array of sailing rows. Unlike the parse-empty case, nothing about the data was judged; the extraction itself returned structurally invalid output, usually because the serialized function threw in-page or was mangled before evaluation.
Source
Thrown at clis/ctrip/ferry.js:66
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,
departureTime: r.departureTime,
fromPort: r.fromPort,
arrivalTime: r.arrivalTime,
toPort: r.toPort,
duration: r.duration,
price: r.price,
status: r.status,
url: searchUrl,View on GitHub (pinned to 49907e53dc)
Solutions
- Rerun to rule out a one-off race; if reproducible, inspect buildFerryExtractJs in clis/ctrip/utils.js and confirm every code path returns an Array
- Run the CLI unbundled (raw ESM via node) — function serialization to page.evaluate is fragile under bundlers/transpilers
- Log the current page URL at extraction time to detect redirects happening mid-command
- Capture console errors from the page during evaluate to find the in-page exception
Example fix
// before (utils.js): throw inside evaluate -> bridge returns undefined
return rows.map(r => {
if (!r.ship) throw new Error('missing');
return {...};
});
// after: never throw in-page; filter instead
return rows.filter(r => r.ship).map(r => ({...})); Defensive patterns
Strategy: type-guard
Validate before calling
// Verify the extractor is a plain serializable function before evaluation
import { buildFerryExtractJs } from './utils.js';
if (typeof buildFerryExtractJs !== 'function') throw new Error('buildFerryExtractJs missing'); Type guard
function isNonArrayExtraction(v) {
return v === null || v === undefined || !Array.isArray(v);
}
const raw = await page.evaluate(buildFerryExtractJs());
if (isNonArrayExtraction(raw)) throw new Error('ferry extractor returned non-array'); Try / catch
try {
return await run(['ctrip', 'ferry', '--from', f, '--to', t, '--date', d]);
} catch (e) {
if (e instanceof Error && /malformed rows/.test(e.message)) {
console.error('Extraction contract broken: run unbundled, check buildFerryExtractJs return types');
throw e;
}
throw e;
} Prevention
- Keep helper modules unbundled/unminified — page.evaluate serialization is fragile
- Make buildFerryExtractJs return an explicit Array on every path; never throw inside evaluate
- Log location.href right before extraction to catch mid-command SPA redirects
- Capture page console errors during evaluate to surface in-page exceptions
When it happens
Trigger: Calling `ctrip ferry <from> <to> --date <d>` where page.evaluate(buildFerryExtractJs()) returns undefined/null/non-array — extraction script threw inside the page (bridge swallows the throw and returns undefined), the function was transpiled into something unserializable, or the evaluation raced a page redirect away from the results URL.
Common situations: Bundling/minifying clis/ctrip/utils.js so the function passed to page.evaluate loses its shape; CSP or injected page scripts interfering with evaluate; CLI/utils version mismatch after partial upgrade; SPA redirect firing between the scroll step and extraction.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Ctrip cruise DOM extraction returned malformed rows
- Ctrip cruise cards rendered but parser did not find required
- Ctrip ferry rows rendered but parser did not find required s
- Ctrip round-trip flight DOM extraction returned malformed ro
- Ctrip round-trip flight cards rendered but parser did not fi
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/a6ac24da41571599.
Report an issue: GitHub.