jackwener/OpenCLI · error · CommandExecutionError

Ctrip hotel detail page did not expose SSR hotel data

Error message

Ctrip hotel detail page did not expose SSR hotel data (state=${String(waitResult)})

What it means

CommandExecutionError thrown when the detail page loaded but WAIT_FOR_HOTEL_DETAIL_JS returned a state other than 'content' or 'captcha', meaning the expected SSR hotel data never appeared (e.g. 'timeout', 'error', 'redirect'). The message embeds the state for diagnosis.

Solutions

  1. Verify the hotelId is valid by opening buildHotelDetailUrl(hotelId) in a browser and checking the state value in the message
  2. Increase the wait timeout in WAIT_FOR_HOTEL_DETAIL_JS if on a slow connection
  3. Update the wait/selector logic to the current Ctrip markup
  4. Retry — transient slowness can cause the timeout state

Example fix

// before
const waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);
if (waitResult !== 'content') throw new CommandExecutionError(`...(state=${waitResult})`);
// after
let waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);
if (waitResult !== 'content') {
  await page.waitForTimeout(3000);
  waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);
}
if (waitResult !== 'content') throw new CommandExecutionError(`...(state=${waitResult})`);
Defensive patterns

Strategy: retry

Validate before calling

// validate the id format before the call
if (!/^\d+$/.test(String(hotelId))) throw new Error(`invalid ctrip hotel id: ${hotelId}`);

Type guard

const isContentState = (state) => state === 'content';

Try / catch

try {
  return await ctrip.hotel({ id });
} catch (e) {
  if (e instanceof CommandExecutionError && /did not expose SSR hotel data/.test(e.message)) {
    await sleep(2000);
    return await ctrip.hotel({ id }); // bounded retry for timeout states
  }
  throw e;
}

Prevention

When it happens

Trigger: WAIT_FOR_HOTEL_DETAIL_JS times out waiting for SSR data, the page redirects to a booking/login flow, the hotelId is invalid and Ctrip serves an error page, or the DOM selector for the SSR state never matches.

Common situations: Typo or nonexistent hotelId producing a 404-ish page; Ctrip A/B redesign removing the awaited selector; slow network causing the internal wait timeout; mobile vs desktop page variant.

Related errors


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

Appendix: source

Thrown at clis/ctrip/hotel.js:45

        { name: 'id', required: true, positional: true, help: 'Numeric Ctrip hotel id (use `ctrip hotel-suggest` to discover; e.g. 375539)' },
    ],
    columns: [
        'hotelId', 'name', 'enName',
        'star', 'score', 'scoreLabel', 'reviewCount', 'ratingBreakdown',
        'facilities', 'checkInOut',
        'cityName', 'address', 'lat', 'lon',
        'url',
    ],
    func: async (page, kwargs) => {
        const hotelId = parseHotelId(kwargs.id);
        const url = buildHotelDetailUrl(hotelId);
        await page.goto(url);
        const waitResult = await page.evaluate(WAIT_FOR_HOTEL_DETAIL_JS);
        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 detail page did not expose SSR hotel data (state=${String(waitResult)})`);
        }
        const detail = await page.evaluate(buildHotelDetailExtractJs());
        if (!detail || typeof detail !== 'object') {
            throw new CommandExecutionError('Ctrip hotel detail SSR extraction returned malformed data');
        }
        if (!detail.hotelId || !detail.name) {
            throw new EmptyResultError('ctrip hotel', `No detail exposed for hotel id ${hotelId}`);
        }
        return [{ ...detail, url }];
    },
});

View on GitHub (pinned to 49907e53dc)