jackwener/OpenCLI · error

Failed to save: HTTP ' + r2.status

Error message

Failed to save: HTTP ' + r2.status

What it means

Thrown when the save POST (POST /api/v1/web/save/<pk>/save/) returns a non-2xx HTTP status. Unlike the feed request, there is no special 404 branch — any failure status is reported as 'Failed to save: HTTP <status>'. Typical causes are an invalid/missing CSRF token (403), an expired session (401/403), rate limiting (429), or a bad pk (404).

Source

Thrown at clis/instagram/save.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 + '/save/', {
    method: 'POST', credentials: 'include',
    headers: { ...headers, 'X-CSRFToken': csrf, 'Content-Type': 'application/x-www-form-urlencoded' },
  });
  if (!r2.ok) throw new Error('Failed to save: HTTP ' + r2.status);
  assertOkStatus(await readInstagramJson(r2, 'Instagram save'), 'Instagram save');
  return [{ status: 'Saved', user: username, post: caption || '(post #' + (idx+1) + ')' }];
})()
` },
    ],
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-login to Instagram in the CLI's browser profile to restore the session and a valid csrftoken cookie, then retry.
  2. If 403, confirm a non-empty csrftoken is present (check document.cookie in the driven browser); a fresh page load of instagram.com usually (re)sets it.
  3. If 429, wait before retrying and avoid batch-saving many posts back-to-back.
  4. If 404, re-run the command — the pk comes from a fresh feed fetch, so a stale/cached invocation may reference a deleted post.
Defensive patterns

Strategy: retry

Try / catch

try {
  await run(['instagram', 'save', username, '--index', String(i)]);
} catch (e) {
  const m = String(e.message).match(/Failed to save: HTTP (\d+)/);
  if (m) {
    const status = Number(m[1]);
    if (status === 401 || status === 403) await refreshInstagramLogin(); // restore session + csrftoken
    if (status === 429) await sleep(5 * 60_000);
    return retryOnce(() => run(['instagram', 'save', username, '--index', String(i)]));
  }
  throw e;
}

Prevention

When it happens

Trigger: POSTing to /api/v1/web/save/<pk>/save/ when the `csrftoken` cookie is absent or expired (regex match fails, empty CSRF sent -> 403), the session cookies are stale (401), the pk is invalid (404), or the account is rate-limited/flagged (429).

Common situations: Session recently expired so document.cookie has no csrftoken; Instagram rotates the CSRF token mid-session; automation heuristics block the write action; saving posts in a rapid loop triggers 429.

Related errors


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