jackwener/OpenCLI · error

Instagram unfollow returned invalid JSON

Error message

Instagram unfollow returned invalid JSON

What it means

Thrown in clis/instagram/unfollow.js when the response body from the friendships/destroy call cannot be parsed as JSON (r2.json() throws, caught by the try/catch). The unfollow flow requires structured confirmation, so an HTML error page, empty body, or non-JSON response is treated as a failure.

Source

Thrown at clis/instagram/unfollow.js:38

        { evaluate: `(async () => {
  const username = \${{ args.username | json }};
  const headers = { 'X-IG-App-ID': '936619743392459' };
  const opts = { credentials: 'include', headers };

  ${buildResolveInstagramUserIdJs()}

  const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';
  const r2 = await fetch('https://www.instagram.com/api/v1/friendships/destroy/' + userId + '/', {
    method: 'POST',
    credentials: 'include',
    headers: { ...headers, 'X-CSRFToken': csrf, 'Content-Type': 'application/x-www-form-urlencoded' },
  });
  if (!r2.ok) throw new Error('Failed to unfollow: HTTP ' + r2.status);
  let d2;
  try {
    d2 = await r2.json();
  } catch {
    throw new Error('Instagram unfollow returned invalid JSON');
  }
  if (!d2 || typeof d2 !== 'object' || d2.status !== 'ok') {
    throw new Error('Instagram unfollow returned no success evidence');
  }
  return [{ status: 'Unfollowed', username }];
})()
` },
    ],
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Capture res.text() in the catch to inspect what was actually returned (usually HTML = auth/bot challenge)
  2. Re-login to refresh the session so Instagram stops redirecting to the login page
  3. Handle any consent/challenge interstitial in the browser before retrying
  4. Check whether Instagram changed the endpoint response format and update parsing

Example fix

// before
catch {
  throw new Error('Instagram unfollow returned invalid JSON');
}
// after
catch (e) {
  const text = await r2.text().catch(() => '');
  throw new Error('Unfollow response not JSON (HTTP ' + r2.status + '): ' + text.slice(0, 200));
}
Defensive patterns

Strategy: type-guard

Validate before calling

const text = await r2.text();
let d2;
try { d2 = JSON.parse(text); } catch { throw new Error('Non-JSON response (login page or challenge?): ' + text.slice(0, 200)); }

Type guard

function isUnfollowSuccess(d) {
  return !!d && typeof d === 'object' && d.status === 'ok';
}

Try / catch

try {
  await unfollow(username);
} catch (e) {
  if (/invalid JSON/.test(e.message)) {
    await relogin(); // body was likely an HTML login/consent page
    await unfollow(username);
  } else throw e;
}

Prevention

When it happens

Trigger: The POST returned r2.ok but the body is not JSON — e.g. a login HTML redirect page, an empty 200 response, a Cloudflare challenge page, or Instagram returning plain-text errors.

Common situations: Session expired mid-flow so Instagram serves the login page with 200, bot-protection/consent interstitials, network proxies injecting HTML, or Instagram API shape changes.

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