{"record":{"id":"25c2de2ad59c1b82","repo":"jackwener/OpenCLI","slug":"instagram-unfollow-returned-invalid-json","errorCode":null,"errorMessage":"Instagram unfollow returned invalid JSON","messagePattern":"Instagram unfollow returned invalid JSON","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"clis/instagram/unfollow.js","lineNumber":38,"sourceCode":"        { evaluate: `(async () => {\n  const username = \\${{ args.username | json }};\n  const headers = { 'X-IG-App-ID': '936619743392459' };\n  const opts = { credentials: 'include', headers };\n\n  ${buildResolveInstagramUserIdJs()}\n\n  const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';\n  const r2 = await fetch('https://www.instagram.com/api/v1/friendships/destroy/' + userId + '/', {\n    method: 'POST',\n    credentials: 'include',\n    headers: { ...headers, 'X-CSRFToken': csrf, 'Content-Type': 'application/x-www-form-urlencoded' },\n  });\n  if (!r2.ok) throw new Error('Failed to unfollow: HTTP ' + r2.status);\n  let d2;\n  try {\n    d2 = await r2.json();\n  } catch {\n    throw new Error('Instagram unfollow returned invalid JSON');\n  }\n  if (!d2 || typeof d2 !== 'object' || d2.status !== 'ok') {\n    throw new Error('Instagram unfollow returned no success evidence');\n  }\n  return [{ status: 'Unfollowed', username }];\n})()\n` },\n    ],\n});\n","sourceCodeStart":20,"sourceCodeEnd":48,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/instagram/unfollow.js#L20-L48","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Capture res.text() in the catch to inspect what was actually returned (usually HTML = auth/bot challenge)","Re-login to refresh the session so Instagram stops redirecting to the login page","Handle any consent/challenge interstitial in the browser before retrying","Check whether Instagram changed the endpoint response format and update parsing"],"exampleFix":"// before\ncatch {\n  throw new Error('Instagram unfollow returned invalid JSON');\n}\n// after\ncatch (e) {\n  const text = await r2.text().catch(() => '');\n  throw new Error('Unfollow response not JSON (HTTP ' + r2.status + '): ' + text.slice(0, 200));\n}","handlingStrategy":"type-guard","validationCode":"const text = await r2.text();\nlet d2;\ntry { d2 = JSON.parse(text); } catch { throw new Error('Non-JSON response (login page or challenge?): ' + text.slice(0, 200)); }","typeGuard":"function isUnfollowSuccess(d) {\n  return !!d && typeof d === 'object' && d.status === 'ok';\n}","tryCatchPattern":"try {\n  await unfollow(username);\n} catch (e) {\n  if (/invalid JSON/.test(e.message)) {\n    await relogin(); // body was likely an HTML login/consent page\n    await unfollow(username);\n  } else throw e;\n}","preventionTips":["Detect HTML bodies (text startsWith '<!DOCTYPE' or '<html') as an auth/challenge signal","Refresh sessions before long batch unfollow runs","Handle consent/bot-challenge interstitials in the browser before API calls","Always validate d2.status === 'ok' even when JSON parses"],"tags":["json","parsing","instagram","session"],"backgroundTag":"invalid-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}