jackwener/OpenCLI · error · CommandExecutionError

Ctrip flight API returned an itinerary without an id

Error message

Ctrip flight API returned an itinerary without an id

What it means

Thrown when an itinerary inside payload.data.flightItineraryList has no non-empty itineraryId after trimming. The library deduplicates itineraries in a Map keyed by itineraryId, so an item without an id cannot be processed and is treated as a malformed record.

Source

Thrown at clis/ctrip/flight.js:96

            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) {
        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];

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Dump responsePreview and inspect the offending itinerary object to confirm which field now holds the id.
  2. Retry the search — placeholder entries are often transient; a fresh query may return clean records.
  3. Update parsing to read the new id field name if Ctrip renamed itineraryId.
  4. As a workaround, filter out id-less itineraries before processing if you control the calling layer upstream of this parser.

Example fix

// before
for (const itinerary of itineraries) {
    const id = cleanString(itinerary?.itineraryId);
    if (!id) throw new CommandExecutionError('Ctrip flight API returned an itinerary without an id');
// after (tolerate id-less placeholder entries)
for (const itinerary of itineraries) {
    const id = cleanString(itinerary?.itineraryId) || cleanString(itinerary?.itineraryID);
    if (!id) continue; // skip placeholder entries instead of failing the whole search
Defensive patterns

Strategy: validation

Validate before calling

// validate each itinerary has an id before processing
const valid = (payload?.data?.flightItineraryList || []).every(
  (it) => typeof it?.itineraryId === 'string' && it.itineraryId.trim() !== ''
);
if (!valid) console.warn('response contains id-less itineraries; expect this error');

Type guard

function hasItineraryId(itinerary) {
  return typeof itinerary?.itineraryId === 'string' && itinerary.itineraryId.trim().length > 0;
}

Try / catch

try {
  const rows = await cli.run(['ctrip', 'flight', from, to, date]);
} catch (err) {
  if (String(err.message).includes('itinerary without an id')) {
    console.error('Ctrip returned a placeholder itinerary; retry the search');
  } else throw err;
}

Prevention

When it happens

Trigger: A captured batchSearch response (status=0, valid array shape) contains at least one itinerary object where itineraryId is missing, null, empty string, or whitespace-only — often a placeholder/degraded entry Ctrip emits for sold-out or unpriceable flights.

Common situations: Seen when Ctrip includes ghost/placeholder itineraries in results, during schema changes that rename itineraryId, or when heavy automation traffic causes Ctrip to degrade some result entries.

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