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
- Re-login to Instagram in the CLI's browser profile to restore the session and a valid csrftoken cookie, then retry.
- 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.
- If 429, wait before retrying and avoid batch-saving many posts back-to-back.
- 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
- Load instagram.com (fresh page) before write operations so the csrftoken cookie is set.
- Re-login when session cookies age out; 401/403 are almost always session/CSRF issues.
- Rate-limit batch saves to avoid 429s.
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
- Instagram private route could not derive CSRF token from bro
- HTTP ' + res.status + ' - make sure you are logged in to Ins
- Failed to follow: HTTP ' + r2.status
- returned no success evidence
- User not found: ' + username : 'HTTP ' + r1.status + ' - mak
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/fe954e5ac0fefe05.
Report an issue: GitHub.