jackwener/OpenCLI · error · CommandExecutionError

Ctrip hotel-search SSR rows were missing required hotelId/na

Error message

Ctrip hotel-search SSR rows were missing required hotelId/name anchors

What it means

CommandExecutionError thrown when the extracted SSR rows all fail the filter(row => row.hotelId && row.name), i.e. after mapping, no row carries both a hotelId and a name. The list was an array (so it parsed) but its entries lack the two required anchor fields, so the library refuses to return empty-looking data.

Source

Thrown at clis/ctrip/hotel-search.js:118

        if (waitResult === 'captcha') {
            throw new AuthRequiredError('hotels.ctrip.com', 'Ctrip is asking for a captcha; complete it in your browser session and retry');
        }
        if (waitResult !== 'content') {
            throw new CommandExecutionError(`Ctrip hotel-search page did not expose SSR hotel list (state=${String(waitResult)})`);
        }
        const raw = await page.evaluate(EXTRACT_HOTELS_JS);
        if (!Array.isArray(raw)) {
            throw new CommandExecutionError('Ctrip hotel-search returned malformed SSR hotel list');
        }
        if (raw.length === 0) {
            throw new EmptyResultError('ctrip hotel-search', `No hotels for city=${cityId} on ${checkin} → ${checkout}`);
        }
        const rows = raw
            .map((entry, i) => mapHotelRow(entry, i))
            .filter((row) => row.hotelId && row.name)
            .slice(0, limit);
        if (rows.length === 0) {
            throw new CommandExecutionError('Ctrip hotel-search SSR rows were missing required hotelId/name anchors');
        }
        return rows;
    },
});

export const __test__ = { parseHotelLimit, assertCheckinBeforeCheckout, WAIT_FOR_SSR_JS, EXTRACT_HOTELS_JS };

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Print one raw entry and update mapHotelRow to map the current field names to hotelId/name
  2. Update the CLI to a version matching current Ctrip markup
  3. Check whether the search params (cityId/checkin/checkout) returned only placeholder cards and use a normal city/date combo

Example fix

// before
const rows = raw.map((entry, i) => mapHotelRow(entry, i)).filter((row) => row.hotelId && row.name);
// after
const rows = raw.map((entry, i) => mapHotelRow(entry, i))
  .map((row) => ({ ...row, hotelId: row.hotelId ?? row.hotelID ?? row.id, name: row.name ?? row.hotelName }))
  .filter((row) => row.hotelId && row.name);
Defensive patterns

Strategy: type-guard

Validate before calling

const rows = raw.map(mapHotelRow).filter((r) => r.hotelId && r.name);
if (rows.length === 0) console.warn('no rows carried hotelId/name anchors — inspect raw entries');

Type guard

const hasAnchors = (row) => typeof row === 'object' && row !== null && typeof row.hotelId === 'string' && row.hotelId.length > 0 && typeof row.name === 'string' && row.name.length > 0;

Try / catch

try {
  const rows = await ctrip.hotelSearch({ cityId, checkin, checkout });
} catch (e) {
  if (e instanceof CommandExecutionError && /hotelId\/name anchors/.test(e.message)) {
    // treat as schema drift: alert, update mapping, or fall back to another source
  } else throw e;
}

Prevention

When it happens

Trigger: EXTRACT_HOTELS_JS returns an array whose objects use different key names (e.g. hotelName/id) or whose fields are nested, so mapHotelRow cannot anchor hotelId/name on any entry; also when Ctrip serves promo/placeholder cards instead of real hotels.

Common situations: Ctrip API shape drift (renamed fields); region-specific pages with different card layouts; holiday/promotional pages with skeleton cards; a stale mapHotelRow mapping table.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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