jackwener/OpenCLI · error · CommandExecutionError

Trip.com poiSearch returned invalid JSON: ${err instanceof E

Error message

Trip.com poiSearch returned invalid JSON: ${err instanceof Error ? err.message : String(err)}

What it means

fetchPoiSearch calls response.json() and re-throws any parse failure as CommandExecutionError with 'Trip.com poiSearch returned invalid JSON: ...'. This means the server responded with a non-JSON body (HTML error page, empty body, truncated response) despite a successful status code.

Source

Thrown at clis/trip/utils.js:851

            headers: { 'content-type': 'application/json', currency: 'USD' },
            body: JSON.stringify({
                key: keyword,
                mode: '0',
                tripType: 'RT',
                Head: { Currency: 'USD', Locale: 'en-US', Source: 'ONLINE', Channel: 'EnglishSite', ClientID: 'opencli-trip' },
            }),
        });
    } catch (err) {
        throw new CommandExecutionError(`Trip.com poiSearch fetch failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    if (!response.ok) {
        throw new CommandExecutionError(`Trip.com poiSearch failed with status ${response.status}`);
    }
    let payload;
    try {
        payload = await response.json();
    } catch (err) {
        throw new CommandExecutionError(`Trip.com poiSearch returned invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
    }
    if (!Array.isArray(payload?.results)) {
        throw new CommandExecutionError('Trip.com poiSearch returned malformed payload: missing results array');
    }
    return payload.results;
}

/**
 * Flatten POI results into a flat suggestion list: each top-level city keeps its
 * own row, and its `childResults` (nearby airports) follow, so a single search
 * surfaces both the city id and the airport codes.
 */
export function flattenPoiResults(results) {
    const rows = [];
    for (const result of results) {
        if (!result || typeof result !== 'object') continue;
        rows.push(result);
        if (Array.isArray(result.childResults)) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the raw response body (curl the endpoint) to see what was actually returned
  2. Handle anti-bot challenges: the HTML body usually indicates Trip.com wants verification or different headers
  3. Retry — truncated responses are often transient
  4. Add a proxy/debug layer (mitm, request logging) to capture the exact body and headers

Example fix

// before
const results = await fetchPoiSearch(...)  // got HTML instead of JSON
// after
try { const r = await fetchPoiSearch(...) } catch (e) {
  if (String(e.message).includes('invalid JSON')) { console.error('non-JSON body — check for anti-bot page'); }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

const looksLikeJson = (s) => typeof s === 'string' && s.trimStart().startsWith('{');

Try / catch

try {
  const results = await searchAttractions(keyword);
} catch (e) {
  if (/poiSearch returned invalid JSON/.test(e.message)) {
    console.error('Non-JSON body received — likely anti-bot page or proxy HTML. Inspect with curl.');
    return retryAfterDelay(() => searchAttractions(keyword), 5000);
  }
  throw e;
}

Prevention

When it happens

Trigger: Trip.com returns an HTML challenge/login page with 200 status; response body truncated mid-stream; empty body on 204; CDN serving a cached HTML error page; charset/BOM issues in the body.

Common situations: Anti-bot interstitials returned with 200; proxy servers injecting their own HTML error pages; flaky mobile connections truncating responses; API returning plain-text rate-limit notices.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/286a348936b2a48b. Report an issue: GitHub.