jackwener/OpenCLI · error · CommandExecutionError
Ctrip flight batchSearch ended before the upstream search re
Error message
Ctrip flight batchSearch ended before the upstream search reported completion
What it means
Thrown after all captured batchSearch entries are parsed when the last observed response's data.context.finished flag is false. Ctrip's batchSearch is paginated/streamed: the page keeps polling until finished===true. If the capture window ends before upstream reports completion, the results may be incomplete, so the library refuses to return partial data.
Source
Thrown at clis/ctrip/flight.js:102
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];
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);View on GitHub (pinned to 49907e53dc)
Solutions
- Increase CAPTURE_TIMEOUT_SECONDS / the surrounding wait timeout to give Ctrip's polling loop more time.
- Retry the search — slow polls are often transient.
- Reduce result pressure (smaller limit, off-peak times) so Ctrip finishes the search faster.
- Check network health / browser stability; ensure the page is not closed or navigated during capture.
- If Ctrip changed the finished flag location, update the parsing in parseBatchSearchCaptures.
Example fix
// before const CAPTURE_TIMEOUT_SECONDS = 12; // after const CAPTURE_TIMEOUT_SECONDS = 30;
Defensive patterns
Strategy: retry
Validate before calling
// not pre-validatable; ensure the page stays open and budget enough wait time
// confirm the browser context is alive before starting the search
if (browserContext.isClosed()) throw new Error('browser context closed; reopen before searching'); Type guard
function reportedFinished(payload) {
return payload?.data?.context?.finished === true;
} Try / catch
try {
const rows = await cli.run(['ctrip', 'flight', from, to, date]);
} catch (err) {
if (String(err.message).includes('ended before the upstream search reported completion')) {
await sleep(3000); // give Ctrip polling more time, then retry
const rows = await cli.run(['ctrip', 'flight', from, to, date]);
} else throw err;
} Prevention
- Raise CAPTURE_TIMEOUT_SECONDS for high-demand routes
- Never close or navigate the captured page while the search runs
- Retry with backoff; Ctrip poll duration varies with load
- Run during off-peak hours for faster upstream completion
When it happens
Trigger: All captured batchSearch responses parsed fine but none (or not the last) had context.finished===true — the capture timeout (CAPTURE_TIMEOUT_SECONDS=12s) elapsed, the browser page was closed/navigated early, or Ctrip is polling slowly for a high-demand route/date.
Common situations: Developers hit this on popular routes where Ctrip takes longer than the capture window, on slow networks, or when the automation harness closes the page or moves on before polling completes.
Related errors
- twitter followers pagination
- FETCH_ERROR
- archive wayback request failed: ${error?.message || error}
- Bilibili creator comparison
- Bilibili relation modify did not verify ${expectedLabel}; la
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/f8fcbf2d92667080.
Report an issue: GitHub.