jackwener/OpenCLI · error · CommandExecutionError

Ctrip cruise DOM extraction returned malformed rows

Error message

Ctrip cruise DOM extraction returned malformed rows

What it means

CommandExecutionError thrown when buildCruiseExtractJs's page.evaluate returns a non-array value, meaning the in-page extraction script did not return the expected array of cruise rows. This is a parser-contract violation: the extraction JS itself failed or was subverted (e.g. page context returned something unexpected, script serialization failed, or Ctrip injected conflicting globals). It signals the CLI's DOM contract with the page broke at the structural level.

Source

Thrown at clis/ctrip/cruise.js:80

        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') {
                throw new EmptyResultError('ctrip cruise', `No cruises currently departing "${port}"`);
            }
            if (portWait !== 'content') {
                throw new CommandExecutionError(`Ctrip cruise port page did not render (state=${String(portWait)})`);
            }
        }

        const raw = await page.evaluate(buildCruiseExtractJs());
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Ctrip cruise DOM extraction returned malformed rows');
        }
        if (raw.length === 0) {
            throw new CommandExecutionError('Ctrip cruise cards rendered but parser did not find required itinerary anchors');
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            title: r.title,
            star: r.star,
            boarding: r.boarding,
            sailingDate: r.sailingDate,
            tags: r.tags,
            price: r.price,
            url: searchUrl,
        }));
    },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Rerun the command — if it reproduces consistently, the extraction script itself is broken, not the page data
  2. Verify buildCruiseExtractJs in clis/ctrip/utils.js returns an explicit array (e.g. `return rows;` where rows is an Array) and is not transpiled into an unserializable closure
  3. Check you are running the CLI unbundled as intended (raw ESM via node), since function serialization to page.evaluate is fragile under bundling
  4. Log the actual page URL at extraction time to rule out a redirect to a non-results page

Example fix

// before (utils.js): implicit/fragile return
const rows = document.querySelectorAll('.route_info');
return { rows }; // object, not array -> triggers 903
// after
const rows = [...document.querySelectorAll('.route_info')];
return rows.map(r => ({...})); // always an array
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate the extraction helper exists and is a plain function before the CLI evaluates it
import { buildCruiseExtractJs } from './utils.js';
if (typeof buildCruiseExtractJs !== 'function') throw new Error('buildCruiseExtractJs missing');

Type guard

function isNonArrayExtraction(v) {
  return v === null || v === undefined || !Array.isArray(v);
}
// in wrapper code:
const raw = await page.evaluate(buildCruiseExtractJs());
if (isNonArrayExtraction(raw)) throw new Error('extractor returned non-array');

Try / catch

try {
  return await run(['ctrip', 'cruise', port]);
} catch (e) {
  if (e instanceof Error && /malformed rows/.test(e.message)) {
    console.error('Extraction contract broken: run CLI unbundled and check buildCruiseExtractJs');
    throw e;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `ctrip cruise <port>` where page.evaluate(buildCruiseExtractJs()) resolves to undefined/null/non-array — typically because the extraction script threw in-page and the bridge returned undefined, or the evaluated function was mangled (minifier/transpile issue) so it no longer returns an array.

Common situations: Bundling/transpiling clis/ctrip/utils.js in a way that breaks the serialized function passed to page.evaluate; page CSP or injected scripts interfering with evaluation; version mismatch where the CLI and its helpers were partially upgraded; a page redirect landing on a non-results URL right before extraction.

Understand the failure class

Related errors


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