jackwener/OpenCLI · error
returned malformed post row
Error message
returned malformed post row
What it means
Thrown by `getPostFromFeed` when the selected feed item has no usable post identifier: `post.pk ?? post.id` is missing, or its string form does not match /^\d+$/. The media `pk` is required to build the save URL (/api/v1/web/save/<pk>/save/), so a row without a numeric pk cannot be saved.
Source
Thrown at clis/instagram/save.js:41
if (!Number.isInteger(idx) || idx < 0) throw new Error('index must be a positive integer');
const headers = { 'X-IG-App-ID': '936619743392459' };
const opts = { credentials: 'include', headers };
async function readInstagramJson(response, label) {
try {
return await response.json();
} catch {
throw new Error(label + ' returned invalid JSON');
}
}
function getPostFromFeed(feed, label) {
if (!feed || typeof feed !== 'object' || !Array.isArray(feed.items)) {
throw new Error(label + ' returned malformed items payload');
}
if (idx >= feed.items.length) throw new Error('Post index ' + (idx + 1) + ' not found');
const post = feed.items[idx];
const pkRaw = post?.pk ?? post?.id;
const pk = typeof pkRaw === 'number' ? String(pkRaw) : (typeof pkRaw === 'string' ? pkRaw.trim() : '');
if (!/^\\d+$/.test(pk)) throw new Error(label + ' returned malformed post row');
const caption = typeof post?.caption?.text === 'string' ? post.caption.text.substring(0, 60) : '';
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' },View on GitHub (pinned to 49907e53dc)
Solutions
- Try a different --index value — the row at that position may be an ad or non-media entry without a pk.
- Dump the item's JSON (or check in DevTools on instagram.com) to find where the numeric pk actually sits in the current schema.
- Update the CLI's pk extraction if Instagram changed the field layout (e.g. nested item.media.pk).
- Save the post manually on the web if the feed keeps returning rows without identifiers.
Defensive patterns
Strategy: type-guard
Type guard
function hasNumericPk(post) {
const raw = post?.pk ?? post?.id;
return (typeof raw === 'number' && Number.isFinite(raw)) ||
(typeof raw === 'string' && /^\d+$/.test(raw.trim()));
} Try / catch
try {
await run(['instagram', 'save', username, '--index', String(i)]);
} catch (e) {
if (String(e.message).includes('returned malformed post row')) {
// feed row has no usable pk (ad/placeholder) — try the next index
return run(['instagram', 'save', username, '--index', String(i + 1)]);
}
throw e;
} Prevention
- If a given index fails, try a neighboring index — ads/placeholders occupy feed rows.
- Update the CLI when Instagram changes where pk/id lives in feed items.
- Prefer saving via a post URL when feed rows repeatedly lack identifiers.
When it happens
Trigger: feed.items[idx] is a non-media object (ad, banner, story tray entry, or `clip`/`awaiting` wrapper) lacking `pk`/`id`, or the id is a non-numeric string, so the pk regex test fails.
Common situations: Instagram injects ads or suggested-post entries into user feeds; carousel/story placeholder rows appear in items; the target post is a reel/clip wrapped in an envelope where pk lives at a nested path; an Instagram schema change renames the id field.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Bilibili creator comparison returned a malformed manuscript
- Instagram post feed returned malformed items for ${username}
- Instagram media metadata returned malformed result
- returned malformed items payload
- Nowcoder detail returned mismatched moment entity ids
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/bd44a2f675a734c2.
Report an issue: GitHub.