{"record":{"id":"abcb1c62b588accf","repo":"jackwener/OpenCLI","slug":"state-verify-fail","errorCode":"STATE_VERIFY_FAIL","errorMessage":"STATE_VERIFY_FAIL: relation did not flip back to Follow within 5s; unfollow may not have been recorded","messagePattern":"STATE_VERIFY_FAIL: relation did not flip back to Follow within 5s; unfollow may not have been recorded","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"clis/tiktok/unfollow.js","lineNumber":77,"sourceCode":"  target.click();\n\n  // TikTok shows a confirm-unfollow dialog. Wait for it to render.\n  const dialogShown = await waitFor(() => buttonExists(CONFIRM_LABELS), { timeoutMs: 3000 });\n  if (dialogShown) {\n    const confirmBtn = findButtonByText(CONFIRM_LABELS);\n    if (!confirmBtn) {\n      throw new Error('BUTTON_NOT_FOUND: confirm-unfollow dialog detected but confirm button could not be located');\n    }\n    confirmBtn.click();\n  }\n\n  const flipped = await waitFor(\n    () => buttonExists(FOLLOW_LABELS) && !buttonExists(RELATION_LABELS),\n    { timeoutMs: 5000 },\n  );\n  if (!flipped) {\n    ensureNoRateLimitOrThrow();\n    throw new Error('STATE_VERIFY_FAIL: relation did not flip back to Follow within 5s; unfollow may not have been recorded');\n  }\n\n  ensureNoRateLimitOrThrow();\n\n  return [{\n    username,\n    url: ${JSON.stringify(TIKTOK_HOST)} + '/@' + encodeURIComponent(username),\n    result: 'unfollowed',\n  }];\n})()\n`;\n}\n\nasync function unfollowUser(page, args) {\n    const username = normalizeUsername(args.username);\n    const throwFailure = (error) => throwButtonWalkerError(error, {\n        authMessage: 'TikTok requires login to unfollow users',\n        failureMessage: `Failed to unfollow @${username}`,","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/tiktok/unfollow.js#L59-L95","documentation":"After clicking unfollow (and confirming), the script waits up to 5s for the button to flip from Following back to Follow. If the flip never happens it throws with code STATE_VERIFY_FAIL, meaning the unfollow action likely was not recorded by TikTok. This is a post-action state verification, not a selector problem.","triggerScenarios":"The confirm dialog was accepted but the relation button never showed a Follow label within 5s: the click didn't register, TikTok silently rejected the action (rate limit/bot detection), the network request behind the unfollow failed, or the page was stale.","commonSituations":"Mass-unfollowing triggering server-side throttling that silently drops the action; slow network exceeding the 5s window; session expired so the mutation is rejected client-side; TikTok UI changing the post-action button state text.","solutions":["Re-run the unfollow for that user after a delay, then verify the relation state before continuing","Slow down bulk unfollows (add delays between actions) to avoid silent rate-limit rejections","Extend the 5s waitFor timeout if network latency is the cause","Check ensureNoRateLimitOrThrow / any rate-limit notices on the page and back off when signaled"],"exampleFix":"// before\nawait unfollowUser(username);\n// after\ntry {\n  await unfollowUser(username);\n} catch (e) {\n  if (e.message.includes('STATE_VERIFY_FAIL')) {\n    await sleep(60000);           // back off, then verify and retry\n    await unfollowUser(username);\n  } else throw e;\n}","handlingStrategy":"retry","validationCode":"const before = await getRelationState(username); // 'Follow' | 'Following'\nif (before !== 'Following') throw new Error(`${username} is not followed; nothing to unfollow`);","typeGuard":"function relationIsFollowed(state) {\n  return state === 'Following' || state === 'Friends';\n}","tryCatchPattern":"async function unfollowSafe(username, attempts = 2) {\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await unfollowUser(username);\n    } catch (e) {\n      if (!e.message.includes('STATE_VERIFY_FAIL') || i === attempts - 1) throw e;\n      await new Promise(r => setTimeout(r, 30000)); // back off before retry\n    }\n  }\n}","preventionTips":["Verify pre-state (Following) before attempting unfollow","Add generous delays between bulk unfollow actions to avoid silent throttling","After STATE_VERIFY_FAIL, independently re-check the relation before retrying","Watch ensureNoRateLimitOrThrow signals and pause the whole batch when rate limits appear"],"tags":["tiktok","automation","state-verification","rate-limit","retry"],"backgroundTag":"action-not-confirmed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}