jackwener/OpenCLI · error

Failed to unsave: HTTP ' + r2.status

Error message

Failed to unsave: HTTP ' + r2.status

What it means

The unsave POST /api/v1/web/save/{pk}/unsave/ returned a non-2xx HTTP status. This is a transport-level failure before the body contract is even checked — typically 401/403 for auth or 429 for rate limiting. The status code is included in the message.

Source

Thrown at clis/instagram/unsave.js:61

    return { pk, caption };
  }
  function assertOkStatus(payload, label) {
    if (!payload || typeof payload !== 'object' || payload.status !== 'ok') {
      throw new Error(label + ' returned no success evidence');
    }
  }

  // web_profile_info answers HTTP 400 for business accounts; feed-by-username needs no user id. See #2234.
  const r1 = await fetch('https://www.instagram.com/api/v1/feed/user/' + encodeURIComponent(username) + '/username/?count=' + (idx + 1), opts);
  if (!r1.ok) throw new Error(r1.status === 404 ? 'User not found: ' + username : 'HTTP ' + r1.status + ' - make sure you are logged in to Instagram');
  const { pk, caption } = getPostFromFeed(await readInstagramJson(r1, 'Instagram feed-by-username'), 'Instagram feed-by-username');

  const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';
  const r2 = await fetch('https://www.instagram.com/api/v1/web/save/' + pk + '/unsave/', {
    method: 'POST', credentials: 'include',
    headers: { ...headers, 'X-CSRFToken': csrf, 'Content-Type': 'application/x-www-form-urlencoded' },
  });
  if (!r2.ok) throw new Error('Failed to unsave: HTTP ' + r2.status);
  assertOkStatus(await readInstagramJson(r2, 'Instagram unsave'), 'Instagram unsave');
  return [{ status: 'Unsaved', user: username, post: caption || '(post #' + (idx+1) + ')' }];
})()
` },
    ],
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-login in the browser profile to refresh sessionid and csrftoken, then retry.
  2. If status is 429, wait (minutes to hours) and reduce request rate.
  3. Confirm csrftoken cookie exists before the call and is sent as X-CSRFToken.

Example fix

// before
const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1] || '';
// after
const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1];
if (!csrf) throw new Error('Not logged in: csrftoken cookie missing');
Defensive patterns

Strategy: retry

Validate before calling

if (!document.cookie.includes('csrftoken') || !document.cookie.includes('sessionid')) {
  throw new Error('Instagram session cookies missing — log in first');
}

Try / catch

try {
  await cli.unsave(user, index);
} catch (e) {
  const m = e.message.match(/HTTP (\d+)/);
  if (m && m[1] === '429') { await delay(15 * 60_000); return retry(); }
  if (m && (m[1] === '401' || m[1] === '403')) { await relogin(); return retry(); }
  throw e;
}

Prevention

When it happens

Trigger: Session cookies expired (401/403), CSRF token mismatch, or unsave throttling (429) on the POST.

Common situations: Long-lived CLI profile where Instagram rotated the session; bulk unsaving triggering 429; missing csrftoken cookie so the request is rejected.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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