jackwener/OpenCLI · error · Error

STATE_VERIFY_FAIL: follow button did not flip to Following w

Error message

STATE_VERIFY_FAIL: follow button did not flip to Following within 5s; relation may not have been recorded

What it means

The follow command clicks the profile page Follow button and then verifies the button text actually flipped to 'Following'/互关 variants within 5 seconds. If it did not, the library cannot confirm the follow relation was recorded, so it throws this typed error instead of silently reporting success. It is thrown inside the injected page script after target.click(), after a pre-click rate-limit check passes.

Source

Thrown at clis/tiktok/follow.js:64

    return [{ username, url: ${JSON.stringify(TIKTOK_HOST)} + '/@' + encodeURIComponent(username), result: 'already-friends' }];
  }
  if (buttonExists(FOLLOWING_LABELS)) {
    return [{ username, url: ${JSON.stringify(TIKTOK_HOST)} + '/@' + encodeURIComponent(username), result: 'already-following' }];
  }

  const followBtn = findButtonByText(FOLLOW_LABELS);
  if (!followBtn) {
    throw new Error('BUTTON_NOT_FOUND: Follow button not on profile page (logged out, private account, or selectors changed)');
  }

  const target = followBtn.closest('button') || followBtn.closest('[role="button"]') || followBtn;
  target.click();

  // State verification: button text should flip to Following / 已关注 etc.
  const flipped = await waitFor(() => buttonExists(ALREADY_LABELS), { timeoutMs: 5000 });
  if (!flipped) {
    ensureNoRateLimitOrThrow();
    throw new Error('STATE_VERIFY_FAIL: follow button did not flip to Following within 5s; relation may not have been recorded');
  }

  // Re-check rate limit AFTER click — TikTok sometimes flashes captcha
  // mid-flight even when the button text appears to flip.
  ensureNoRateLimitOrThrow();

  return [{
    username,
    url: ${JSON.stringify(TIKTOK_HOST)} + '/@' + encodeURIComponent(username),
    result: 'followed',
  }];
})()
`;
}

async function followUser(page, args) {
    const username = normalizeUsername(args.username);
    const throwFailure = (error) => throwButtonWalkerError(error, {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the command — TikTok dedupes follow, so a retry is safe (the error is marked retryable in RETRYABLE_HINTS.relationFailure).
  2. Wait and back off: pause several minutes before retrying to clear TikTok's rate limit / captcha window.
  3. Verify manually in a browser that the account was followed despite the UI not flipping.
  4. Use a warmer, residential session/cookies and reduce follow frequency.
  5. Check the target profile's locale; if the button uses an unsupported language label, extend the label list in follow.js.

Example fix

// before: single attempt
const rows = await followUser(page, { username: 'someuser' });
// after: retry with backoff on state-verify failure
for (let i = 0; i < 3; i++) {
  try { rows = await followUser(page, { username: 'someuser' }); break; }
  catch (e) {
    if (!/STATE_VERIFY_FAIL/.test(e.message)) throw e;
    await new Promise(r => setTimeout(r, 5000 * (i + 1)));
  }
}
Defensive patterns

Strategy: retry

Validate before calling

// before following, ensure a fresh logged-in session and sane pacing
if (!(await isLoggedIn(page))) throw new Error('login required before follow');
if (Date.now() - lastFollowAt < 15000) throw new Error('throttle: wait between follows');

Type guard

function isStateVerifyFail(e) {
  return e instanceof Error && e.message.includes('STATE_VERIFY_FAIL');
}

Try / catch

try {
  rows = await followUser(page, { username });
} catch (e) {
  if (isStateVerifyFail(e)) {
    await sleep(30_000);            // back off for captcha/rate window
    rows = await followUser(page, { username }); // safe: TikTok dedupes
  } else throw e;
}

Prevention

When it happens

Trigger: Clicking the Follow button on tiktok.com/@<username> when the click did not take effect: TikTok rendered a captcha or rate-limit interstitial that the check missed, the DOM re-rendered and replaced the button mid-flight, a slow network delayed the optimistic UI flip beyond the 5s waitFor timeout, or the button was for a private/restricted account that does not flip.

Common situations: Automating follows in quick succession hits TikTok's action rate limiting; running from a datacenter IP flagged for bot traffic; logged-in session with weakened permissions; localized UI showing a label not in FOLLOWING_LABELS (non en/zh/ja locales).

Related errors


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