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
- Log the offending itinerary object at the given index to see which field is missing or renamed.
- Check whether Ctrip renamed flightSegments/priceList/flightNo/sortPrice/adultPrice and update mapItinerary accordingly.
- Retry the search — sold-out or placeholder itineraries are often transient.
- Make parsing tolerant: skip instead of throwing when a single itinerary is malformed, if partial results are acceptable.
- 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
- Log the failing itinerary index and raw object when this fires
- Track Ctrip field renames (flightSegments, priceList, sortPrice, adultPrice)
- Prefer skipping bad rows over failing the whole search when partial results suffice
- Verify locale/date-time formats still match timePart's regex after locale changes
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Ctrip flight API returned a malformed batchSearch payload
- Ctrip flight API returned an itinerary without an id
- Failed to parse 12306 station_name.js: no station records fo
- archive snapshots returned malformed CDX payload: snapshot r
- archive snapshots returned malformed CDX payload: snapshot r
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/9663998f1fe2b0dc.
Report an issue: GitHub.