jackwener/OpenCLI · error

${label} returned invalid JSON

Error message

${label} returned invalid JSON

What it means

readInstagramJson in the in-page resolver wraps response.json(); if the body is not valid JSON it throws '<label> returned invalid JSON'. This surfaces when Instagram's web_profile_info or feed-by-username endpoints return non-JSON bodies (HTML login/challenge pages, empty or truncated bodies) with an HTTP status that passed the earlier ok checks.

Source

Thrown at clis/instagram/_shared/user-id.js:20

 * In-page snippet that resolves `username` to a numeric user id in `userId`.
 *
 * `web_profile_info` answers HTTP 400 for business and professional accounts,
 * so the commands that need an id fall back to feed-by-username. Its root
 * `user.pk` is the profile owner; `items[0].user.pk` can be a pinned collab
 * author. Callers must already have `username` and `opts` in scope.
 */
export function buildResolveInstagramUserIdJs() {
    return `
  function normalizeInstagramUserId(value, label) {
    const id = typeof value === 'number' ? String(value) : (typeof value === 'string' ? value.trim() : '');
    if (!/^\\d+$/.test(id)) throw new Error(label);
    return id;
  }
  async function readInstagramJson(response, label) {
    try {
      return await response.json();
    } catch {
      throw new Error(label + ' returned invalid JSON');
    }
  }
  function throwInstagramHttpError(response, label, username) {
    if (response.status === 404) throw new Error('User not found: ' + username);
    if (response.status === 401 || response.status === 403) {
      throw new Error('HTTP ' + response.status + ' - make sure you are logged in to Instagram');
    }
    throw new Error(label + ' failed: HTTP ' + response.status);
  }
  const r1 = await fetch('https://www.instagram.com/api/v1/users/web_profile_info/?username=' + encodeURIComponent(username), opts);
  if (r1.status === 404) throw new Error('User not found: ' + username);
  if (!r1.ok && r1.status !== 400) throwInstagramHttpError(r1, 'Instagram web_profile_info', username);
  let userId = r1.ok ? normalizeInstagramUserId((await readInstagramJson(r1, 'Instagram web_profile_info'))?.data?.user?.id, 'Instagram web_profile_info returned no valid user id for: ' + username) : '';
  if (!userId) {
    const r1b = await fetch('https://www.instagram.com/api/v1/feed/user/' + encodeURIComponent(username) + '/username/?count=1', opts);
    if (!r1b.ok) throwInstagramHttpError(r1b, 'Instagram feed-by-username', username);
    userId = normalizeInstagramUserId((await readInstagramJson(r1b, 'Instagram feed-by-username'))?.user?.pk, 'Instagram feed returned no valid profile owner for: ' + username);
  }`;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-login / refresh session cookies — HTML responses at 200 almost always mean the session is gone.
  2. Log response status and the first bytes of the body to confirm an HTML challenge page.
  3. Retry after backoff if it is a transient rate-limit interstitial.
  4. Change IP / use a residential proxy if Instagram is challenge-blocking the current IP.
  5. Ensure request opts include proper headers/credentials so the API returns JSON.

Example fix

// before
const userId = await page.evaluate(buildResolveInstagramUserIdJs());
// after
try {
  return await page.evaluate(buildResolveInstagramUserIdJs());
} catch (e) {
  if (String(e.message).includes('returned invalid JSON')) {
    await refreshInstagramSession(page);
    return page.evaluate(buildResolveInstagramUserIdJs());
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

const cookies = await page.context().cookies('https://www.instagram.com');
if (!cookies.some(c => c.name === 'sessionid')) throw new Error('Session expired — user-id resolution would get HTML');

Type guard

function looksLikeJson(text) {
  return /^\s*[{[]/.test(text);
}

Try / catch

try {
  return await page.evaluate(buildResolveInstagramUserIdJs());
} catch (e) {
  if (String(e.message).includes('returned invalid JSON')) {
    await refreshInstagramSession(page);
    return page.evaluate(buildResolveInstagramUserIdJs()); // retry once
  }
  throw e;
}

Prevention

When it happens

Trigger: response.json() rejects inside readInstagramJson for either fetch — typically because the body is HTML (login redirect, challenge page) or empty despite an otherwise acceptable HTTP status.

Common situations: Expired session cookies so Instagram serves the login HTML page with 200; IP rate-limited with an HTML interstitial; proxy truncating the body; content-type changed by Instagram.

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