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
- Log the full captured responsePreview for the failing entry to see the actual payload shape.
- Diff the observed payload against the expected schema (data.flightItineraryList, data.context.finished) to identify renamed or moved fields.
- Update the parsing in parseBatchSearchCaptures (clis/ctrip/flight.js:90-93) to match the new schema.
- If Ctrip is A/B testing, pin a stable site variant or handle both payload shapes.
- 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
- Pin/monitor the Ctrip site version you automate against
- Keep the capture pattern and parser in sync with the current batchSearch schema
- Alert on this error so schema changes are detected early
- Persist raw responsePreview samples for regression testing the parser
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.
- 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 an itinerary without an id
- Ctrip flight API returned malformed itinerary at index ${ind
- Failed to parse 12306 station_name.js: no station records fo
- Bilibili user search returned malformed result for ${input}
- Ctrip flight API failed (status=${String(payload?.status)}):
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/7f507fd8bc8f79c0.
Report an issue: GitHub.