jackwener/OpenCLI · error · CommandExecutionError

Ctrip flight API returned a malformed batchSearch payload

Error message

Ctrip flight API returned a malformed batchSearch payload

What it means

Thrown when a captured batchSearch response has status 0 but its body does not match the expected schema: `payload.data.flightItineraryList` is not an array, or `payload.data.context.finished` is not a boolean. The library treats this as a contract violation — Ctrip returned a 'successful' payload whose shape the parser cannot trust.

Source

Thrown at clis/ctrip/flight.js:92

        if (entry?.responseBodyTruncated === true) {
            throw new CommandExecutionError('Ctrip flight API response exceeded the browser capture limit');
        }
        if (typeof entry?.responsePreview !== 'string') {
            throw new CommandExecutionError('Ctrip flight API response body was unavailable');
        }
        let payload;
        try {
            payload = JSON.parse(entry.responsePreview);
        }
        catch {
            throw new CommandExecutionError('Ctrip flight API returned invalid JSON');
        }
        if (payload?.status !== 0) {
            throw new CommandExecutionError(`Ctrip flight API failed (status=${String(payload?.status)}): ${cleanString(payload?.msg) || 'unknown error'}`);
        }
        const itineraries = payload?.data?.flightItineraryList;
        if (!Array.isArray(itineraries) || typeof payload?.data?.context?.finished !== 'boolean') {
            throw new CommandExecutionError('Ctrip flight API returned a malformed batchSearch payload');
        }
        for (const itinerary of itineraries) {
            const id = cleanString(itinerary?.itineraryId);
            if (!id) throw new CommandExecutionError('Ctrip flight API returned an itinerary without an id');
            byId.set(id, itinerary);
        }
        finished = payload.data.context.finished;
    }
    if (!finished) {
        throw new CommandExecutionError('Ctrip flight batchSearch ended before the upstream search reported completion');
    }
    return [...byId.values()];
}

function mapItinerary(itinerary, searchUrl, index) {
    const segments = itinerary?.flightSegments;
    const prices = itinerary?.priceList;
    if (!Array.isArray(segments) || segments.length === 0 || !Array.isArray(prices) || prices.length === 0) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the full captured responsePreview for the failing entry to see the actual payload shape.
  2. Diff the observed payload against the expected schema (data.flightItineraryList, data.context.finished) to identify renamed or moved fields.
  3. Update the parsing in parseBatchSearchCaptures (clis/ctrip/flight.js:90-93) to match the new schema.
  4. If Ctrip is A/B testing, pin a stable site variant or handle both payload shapes.
  5. Retry later if it looks like a transient partial/degraded response from Ctrip.

Example fix

// before: trusting the shape blindly
const itineraries = payload?.data?.flightItineraryList;
// after: defensively inspect before calling the library path
const itineraries = payload?.data?.flightItineraryList;
if (payload?.status !== 0 || !Array.isArray(itineraries)) {
  console.error('unexpected payload:', entry.responsePreview.slice(0, 500));
}
Defensive patterns

Strategy: type-guard

Validate before calling

// after capture, before trusting results
const payload = JSON.parse(entry.responsePreview);
const shaped = typeof payload?.status === 'number'
  && Array.isArray(payload?.data?.flightItineraryList)
  && typeof payload?.data?.context?.finished === 'boolean';

Type guard

function hasBatchSearchShape(payload) {
  return payload != null
    && typeof payload === 'object'
    && payload.status === 0
    && Array.isArray(payload?.data?.flightItineraryList)
    && typeof payload?.data?.context?.finished === 'boolean';
}

Try / catch

try {
  const rows = await cli.run(['ctrip', 'flight', from, to, date]);
} catch (err) {
  if (String(err.message).includes('malformed batchSearch payload')) {
    console.error('Ctrip schema changed; capture and inspect the raw responsePreview');
  } else throw err;
}

Prevention

When it happens

Trigger: Ctrip returns status=0 with missing/renamed data fields (data absent, flightItineraryList renamed or null, context or context.finished missing/renamed), typically after an unannounced Ctrip API schema change or an A/B-test variant response.

Common situations: Developers encounter this when Ctrip ships a new response schema version, when the page requests a different endpoint variant than the captured pattern expects, or when regional Ctrip sites return localized/altered payload shapes.

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/7f507fd8bc8f79c0. Report an issue: GitHub.