jackwener/OpenCLI · error · CommandExecutionError

Trip.com flight DOM extraction returned malformed rows${reas

Error message

Trip.com flight DOM extraction returned malformed rows${reason}

What it means

CommandExecutionError thrown when buildFlightExtractJs() returns a non-array value: the flight-card extractor failed rather than producing a row list. When the non-array value carries an in-page error matching /^malformed flight card \d+: [a-z /]+$/, that per-card reason is appended to the message, pinpointing which card and field failed to parse.

Source

Thrown at clis/trip/flight-round.js:73

        }
        const limit = parseListLimit(kwargs.limit);

        const searchUrl = buildFlightRoundSearchUrl(fromCode, toCode, depart, ret);
        await page.goto(searchUrl);
        const waitResult = await page.evaluate(WAIT_FOR_FLIGHTS_JS);
        if (waitResult === 'captcha') {
            throw new AuthRequiredError('trip.com', 'Trip.com is asking for a verification; complete it in your browser session and retry');
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Trip.com flight page did not render flight cards (state=${String(waitResult)})`);
        }
        const raw = await page.evaluate(buildFlightExtractJs());
        if (!Array.isArray(raw)) {
            const reason = raw && typeof raw === 'object' && typeof raw.error === 'string'
                && /^malformed flight card \d+: [a-z /]+$/.test(raw.error)
                ? `: ${raw.error}`
                : '';
            throw new CommandExecutionError(`Trip.com flight DOM extraction returned malformed rows${reason}`);
        }
        if (raw.length === 0) {
            throw new EmptyResultError('trip flight-round', `No round-trip flights for ${fromCode} to ${toCode} on ${depart} .. ${ret}`);
        }
        return raw.slice(0, limit).map((r, i) => ({
            rank: i + 1,
            airline: r.airline,
            departureTime: r.departureTime,
            departureAirport: r.departureAirport,
            arrivalTime: r.arrivalTime,
            arrivalAirport: r.arrivalAirport,
            duration: r.duration,
            stops: r.stops,
            price: r.price,
            currency: r.currency,
            url: searchUrl,
        }));
    },

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the appended reason (e.g. 'malformed flight card 3: missing price') to identify the failing card/field and rerun — partial rendering often resolves on retry
  2. Loosen or add fallback selectors in buildFlightExtractJs for the field reported missing
  3. Filter sponsored/promo cards before extraction so variant markup is skipped
  4. Update the CLI package if the extractor has been fixed upstream

Example fix

// before
const reason = raw && typeof raw === 'object' && typeof raw.error === 'string'
    && /^malformed flight card \d+: [a-z /]+$/.test(raw.error) ? `: ${raw.error}` : '';
throw new CommandExecutionError(`Trip.com flight DOM extraction returned malformed rows${reason}`);
// after
// In buildFlightExtractJs, skip cards missing optional fields instead of failing:
// const price = card.querySelector('.price')?.textContent?.trim();
// if (!price) return null; // filter nulls in the caller
const rows = raw.filter(Boolean);
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm results actually rendered and look complete before extraction
const cardCount = await page.evaluate(() =>
    document.querySelectorAll('[class*=flight-card], [data-flight-id]').length);
if (cardCount > 0) console.log(`${cardCount} flight cards rendered; extractor should parse all or fail with a per-card reason`);

Type guard

function isFlightRows(v) {
    return Array.isArray(v) && v.every(r => r && typeof r === 'object' && 'rank' in r);
}
function hasCardError(v): v is { error: string } {
    return !!v && typeof v === 'object' && typeof (v as any).error === 'string';
}

Try / catch

try {
    const flights = await runFlightRound(args);
} catch (e) {
    if (e instanceof CommandExecutionError && /malformed rows/.test(e.message)) {
        // log e.message (includes per-card reason) and retry once; persistent failures mean markup drift
    } else { throw e; }
}

Prevention

When it happens

Trigger: page.evaluate(buildFlightExtractJs()) resolves to something other than an array — e.g. {error: 'malformed flight card 3: missing price'} — after flights rendered but a card lacked an expected field; empty results take a different path (EmptyResultError) and never reach this throw.

Common situations: Partially rendered results where some cards lack price/duration nodes; Trip.com mixing sponsored cards with different markup into the results list; extractor field regexes too strict for a new card variant; mid-navigation aborting evaluation.

Understand the failure class

Related errors


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