jackwener/OpenCLI · error · CommandExecutionError

Ctrip flight API returned malformed itinerary at index ${ind

Error message

Ctrip flight API returned malformed itinerary at index ${index}

What it means

Thrown by mapItinerary when an itinerary lacks the minimum data needed to render a row: flightSegments is not a non-empty array, priceList is not a non-empty array, or any required display field (airline, flightNo, departure/arrival time, airport names, numeric price) is missing after mapping. Each itinerary from the API is expected to contain at least one segment and one price; anything else is treated as malformed.

Source

Thrown at clis/ctrip/flight.js:111

        }
        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) {
        throw new CommandExecutionError(`Ctrip flight API returned malformed itinerary at index ${index}`);
    }
    const legs = segments.flatMap((segment) => Array.isArray(segment?.flightList) ? segment.flightList : []);
    const first = legs[0];
    const last = legs.at(-1);
    const airline = [...new Set(segments.map((segment) => cleanString(segment?.airlineName)).filter(Boolean))].join(' / ');
    const flightNo = [...new Set(legs.map((leg) => cleanString(leg?.flightNo)).filter(Boolean))].join(' / ');
    const aircraft = [...new Set(legs.map((leg) => cleanString(leg?.aircraftName)).filter(Boolean))].join(' / ') || null;
    const departureTime = timePart(first?.departureDateTime);
    const arrivalTime = timePart(last?.arrivalDateTime);
    const departureAirport = cleanString(first?.departureAirportName);
    const arrivalAirport = cleanString(last?.arrivalAirportName);
    const price = Number(prices[0]?.sortPrice ?? prices[0]?.adultPrice);
    if (!airline || !flightNo || !departureTime || !arrivalTime || !departureAirport || !arrivalAirport || !Number.isFinite(price)) {
        throw new CommandExecutionError(`Ctrip flight API returned malformed itinerary at index ${index}`);
    }
    const row = {
        airline,
        flightNo,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the offending itinerary object at the given index to see which field is missing or renamed.
  2. Check whether Ctrip renamed flightSegments/priceList/flightNo/sortPrice/adultPrice and update mapItinerary accordingly.
  3. Retry the search — sold-out or placeholder itineraries are often transient.
  4. Make parsing tolerant: skip instead of throwing when a single itinerary is malformed, if partial results are acceptable.
  5. Verify date-time formats still match the regex in timePart if departure/arrival times come back empty.

Example fix

// before
const price = Number(prices[0]?.sortPrice ?? prices[0]?.adultPrice);
// after (fall back to any available price field)
const p = prices[0] || {};
const price = Number(p.sortPrice ?? p.adultPrice ?? p.price ?? p.minPrice);
if (!Number.isFinite(price)) {
  console.warn('skipping malformed itinerary', index);
  return null;
}
Defensive patterns

Strategy: validation

Validate before calling

// validate itinerary essentials before expecting clean rows
const ok = (it) => Array.isArray(it?.flightSegments) && it.flightSegments.length > 0
  && Array.isArray(it?.priceList) && it.priceList.length > 0
  && Number.isFinite(Number(it.priceList[0]?.sortPrice ?? it.priceList[0]?.adultPrice));
const bad = (payload?.data?.flightItineraryList || []).filter((it) => !ok(it));
if (bad.length) console.warn(`${bad.length} malformed itineraries expected to be rejected`);

Type guard

function isRenderableItinerary(itinerary) {
  return Array.isArray(itinerary?.flightSegments) && itinerary.flightSegments.length > 0
    && Array.isArray(itinerary?.priceList) && itinerary.priceList.length > 0
    && Number.isFinite(Number(itinerary.priceList[0]?.sortPrice ?? itinerary.priceList[0]?.adultPrice));
}

Try / catch

try {
  const rows = await cli.run(['ctrip', 'flight', from, to, date]);
} catch (err) {
  const m = String(err.message).match(/malformed itinerary at index (\d+)/);
  if (m) console.error(`itinerary #${m[1]} lacks segments/prices; inspect raw payload or retry`);
  else throw err;
}

Prevention

When it happens

Trigger: A deduplicated itinerary has empty/missing flightSegments or priceList, or its nested flightList legs lack flightNo/airlineName/departureDateTime/arrivalDateTime/airport names, or prices[0] lacks both sortPrice and adultPrice (so price is NaN).

Common situations: Occurs with Ctrip schema changes that rename fields (e.g. sortPrice/adultPrice), sold-out itineraries with empty priceList, charter/placeholder entries with no flight legs, or locale-dependent date-time formats that timePart cannot parse.

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