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
- Retry the command — TikTok dedupes follow, so a retry is safe (the error is marked retryable in RETRYABLE_HINTS.relationFailure).
- Wait and back off: pause several minutes before retrying to clear TikTok's rate limit / captcha window.
- Verify manually in a browser that the account was followed despite the UI not flipping.
- Use a warmer, residential session/cookies and reduce follow frequency.
- 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
- Space out write actions (follows) by seconds-to-minutes to avoid rate limiting
- Use a warmed, logged-in residential session rather than a fresh datacenter profile
- Retry on STATE_VERIFY_FAIL — follows are idempotent on TikTok
- Check the rendered page for captcha elements before clicking
- Keep the Following/Friends label list current for your account locale
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
- STATE_VERIFY_FAIL
- ChatGPT model did not switch to ${target.label}.
- ${send?.reason || 'Failed to send Grok prompt'}
- BUTTON_NOT_FOUND: Post button never became enabled (text not
- STATE_VERIFY_FAIL: comment count did not increase within 8s;
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/af8681823d873989.
Report an issue: GitHub.