jackwener/OpenCLI · error

' + label + ' returned no success evidence

Error message

' + label + ' returned no success evidence

What it means

The unsave POST returned a body without status:'ok', so the CLI cannot confirm the post was unsaved. assertOkStatus enforces Instagram's success contract; any other body (error object, empty JSON) fails. HTTP status was 200 but the operation itself did not succeed.

Solutions

  1. Refresh the session and csrftoken cookie by logging in again in the browser profile.
  2. Check whether the post was already unsaved; treat this as success then.
  3. Retry after a delay to clear soft rate limits.
Defensive patterns

Strategy: retry

Validate before calling

const csrf = document.cookie.match(/csrftoken=([^;]+)/)?.[1];
if (!csrf) throw new Error('Missing csrftoken — log in first');

Type guard

const saveOk = (d) => !!d && typeof d === 'object' && d.status === 'ok';

Try / catch

try {
  await cli.unsave(user, index);
} catch (e) {
  if (e.message.includes('no success evidence')) {
    await delay(2000);
    await cli.unsave(user, index); // retry once after session refresh
  } else throw e;
}

Prevention

When it happens

Trigger: POST /api/v1/web/save/{pk}/unsave/ returns 200 with an error payload — missing/invalid CSRF token, post already unsaved, or session rejected server-side.

Common situations: Stale csrftoken cookie; running unsave twice on the same post; Instagram silently rejecting automation.

Related errors


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

Appendix: source

Thrown at clis/instagram/unsave.js:47

    } 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 + '/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)