jackwener/OpenCLI · error · Error
BUTTON_NOT_FOUND
BUTTON_NOT_FOUND
Error message
BUTTON_NOT_FOUND: neither Follow nor Following button found (page not rendered, blocked, or selectors changed)
What it means
The unfollow script builds a browser script that locates the Follow/Following relation button by text (RELATION_LABELS). If no such element exists it throws Error with code BUTTON_NOT_FOUND, refusing to silently succeed on a page that never rendered the profile action row. This guards against unfollowing based on a broken or blocked page.
Source
Thrown at clis/tiktok/unfollow.js:55
ensureLoggedInOrThrow();
ensureNoRateLimitOrThrow();
const FOLLOW_LABELS = ['Follow', '关注', 'フォロー'];
const FOLLOWING_LABELS = ['Following', '已关注', 'フォロー中'];
const FRIENDS_LABELS = ['Friends', '互关', 'フレンド'];
const RELATION_LABELS = FOLLOWING_LABELS.concat(FRIENDS_LABELS);
const CONFIRM_LABELS = ['Unfollow', '取消关注'];
// Idempotent fast path: not currently following.
if (!buttonExists(RELATION_LABELS) && buttonExists(FOLLOW_LABELS)) {
return [{ username, url: ${JSON.stringify(TIKTOK_HOST)} + '/@' + encodeURIComponent(username), result: 'already-not-following' }];
}
const relationBtn = findButtonByText(RELATION_LABELS);
if (!relationBtn) {
// Neither Follow nor Following — page may not have rendered, or
// private account / blocked: refuse to silently succeed.
throw new Error('BUTTON_NOT_FOUND: neither Follow nor Following button found (page not rendered, blocked, or selectors changed)');
}
const target = relationBtn.closest('button') || relationBtn.closest('[role="button"]') || relationBtn;
target.click();
// TikTok shows a confirm-unfollow dialog. Wait for it to render.
const dialogShown = await waitFor(() => buttonExists(CONFIRM_LABELS), { timeoutMs: 3000 });
if (dialogShown) {
const confirmBtn = findButtonByText(CONFIRM_LABELS);
if (!confirmBtn) {
throw new Error('BUTTON_NOT_FOUND: confirm-unfollow dialog detected but confirm button could not be located');
}
confirmBtn.click();
}
const flipped = await waitFor(
() => buttonExists(FOLLOW_LABELS) && !buttonExists(RELATION_LABELS),
{ timeoutMs: 5000 },View on GitHub (pinned to 49907e53dc)
Solutions
- Ensure a logged-in session and that the profile page loads fully (check screenshot/HTML) before unfollowing
- Verify the target username's profile renders a Follow/Following button in a normal browser
- Update RELATION_LABELS / findButtonByText to match current button text and locale if TikTok changed the UI
- Retry with a real-browser fingerprint (less headless detection) if a challenge page is being served
Defensive patterns
Strategy: validation
Validate before calling
await page.goto(`https://www.tiktok.com/@${username}`, { waitUntil: 'networkidle' });
const hasRelationBtn = await page.evaluate(() => document.body.innerText.includes('Follow') || document.body.innerText.includes('Following'));
if (!hasRelationBtn) throw new Error(`Profile page for ${username} did not render a relation button`); Type guard
function relationButtonExists(doc, labels) {
return labels.some(l => Array.from(doc.querySelectorAll('button, [role="button"]')).some(el => el.textContent.trim().toLowerCase().includes(l.toLowerCase())));
} Try / catch
try {
await unfollowUser(username);
} catch (e) {
if (e.message.includes('BUTTON_NOT_FOUND')) {
console.error(`Cannot unfollow ${username}: page not rendered, blocked, or selectors changed`);
} else {
throw e;
}
} Prevention
- Ensure a logged-in session; logged-out profiles render different layouts
- Screenshot the page on failure to distinguish bot walls from selector drift
- Keep RELATION_LABELS in sync with the current TikTok UI and locales
- Do not attempt unfollows on private/blocked accounts — pre-check reachability
When it happens
Trigger: buildUnfollowScript's findButtonByText(RELATION_LABELS) returns null: profile page didn't render (bot wall/captcha), navigation timed out, account is private or blocked you, or TikTok changed the button text/attributes so labels no longer match.
Common situations: Not logged in so profile shows a different layout; headless browser detected and served a challenge; profile of a suspended user; RELATION_LABELS text (e.g. localized 'Follow') no longer matching after a TikTok UI or locale change.
Related errors
- Favorites button not found - make sure you are logged in
- Like button not found - make sure you are logged in
- BUTTON_NOT_FOUND: Follow button not on profile page (logged
- Could not find Antigravity input box
- Could not find input box
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/e1cd806fb656ca19.
Report an issue: GitHub.