{"record":{"id":"9c118f64a074806a","repo":"jackwener/OpenCLI","slug":"button-not-found-post-button-never-became-enabled","errorCode":null,"errorMessage":"BUTTON_NOT_FOUND: Post button never became enabled (text not registered or selectors changed)","messagePattern":"BUTTON_NOT_FOUND: Post button never became enabled \\(text not registered or selectors changed\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"clis/tiktok/comment.js","lineNumber":79,"sourceCode":"  }\n\n  input.focus();\n  // execCommand is deprecated but still the only reliable way to inject\n  // text into TikTok's contenteditable so its React tree picks up the\n  // value; replicating with InputEvent fires but TikTok ignores it.\n  document.execCommand('insertText', false, commentText);\n\n  // Wait for the post button to become enabled — TikTok disables it\n  // until non-empty text is detected by their input handler.\n  const postReady = await waitFor(() => {\n    const candidate = findButtonByText(['Post', '发布', '发送']);\n    if (!candidate) return false;\n    const ariaDisabled = candidate.getAttribute && candidate.getAttribute('aria-disabled');\n    return !candidate.disabled && ariaDisabled !== 'true';\n  }, { timeoutMs: 4000 });\n  if (!postReady) {\n    ensureNoRateLimitOrThrow();\n    throw new Error('BUTTON_NOT_FOUND: Post button never became enabled (text not registered or selectors changed)');\n  }\n\n  const postBtn = findButtonByText(['Post', '发布', '发送']);\n  postBtn.click();\n\n  // State verification: a new comment-level-1 element should appear.\n  const flipped = await waitFor(\n    () => document.querySelectorAll('[data-e2e=\"comment-level-1\"]').length > beforeCount,\n    { timeoutMs: 8000 },\n  );\n  if (!flipped) {\n    ensureNoRateLimitOrThrow();\n    throw new Error('STATE_VERIFY_FAIL: comment count did not increase within 8s; comment may or may not have been recorded server-side');\n  }\n\n  ensureNoRateLimitOrThrow();\n\n  return [{","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/tiktok/comment.js#L61-L97","documentation":"After typing the comment text, `buildCommentScript` waits up to 4 seconds for the Post/发布/发送 button to become enabled. If it never enables (or is never found), the script calls `ensureNoRateLimitOrThrow()` and then throws `BUTTON_NOT_FOUND: Post button never became enabled ...`. Typically the typed text never registered in TikTok's React-controlled contenteditable, or a rate limit is suppressing the button state.","triggerScenarios":"Calling `tiktok comment` when execCommand text injection didn't trigger TikTok's React state so the Post button stays disabled; the comment text is empty or only whitespace after injection; TikTok is rate-limiting the account's commenting; the button's selector/label changed in a frontend update.","commonSituations":"Commenting repeatedly in a short window (account soft-limited by TikTok); special characters or emoji breaking the injection; headless browser where contenteditable focus/insert events don't propagate; TikTok redesign renaming button labels or aria-disabled semantics.","solutions":["Check for and respect a TikTok comment rate limit — wait (minutes to hours) before retrying; the script already routes rate-limit cases through ensureNoRateLimitOrThrow first.","Re-run the command; a single failed injection is often transient.","Simplify the comment text (plain ASCII, no leading/trailing whitespace) to rule out injection/encoding issues.","Verify in a headed browser that typing manually enables the Post button; if manual typing also fails, the account/video is restricted.","If buttons exist but never enable, TikTok changed the button markup/labels — update the selector/text list in buildCommentScript."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// avoid predictable automation: non-empty plain text, sane pacing\nconst safeText = text.trim();\nif (!safeText) throw new Error('comment text is empty');\nawait enforceMinInterval(accountId, 60 * 1000); // keep >1 min between comments per account","typeGuard":null,"tryCatchPattern":"try {\n  await tiktok.comment({ url: videoUrl, text });\n} catch (e) {\n  if (String(e.message).startsWith('BUTTON_NOT_FOUND') && /Post button/.test(e.message)) {\n    if (await isRateLimited(accountId)) {\n      await sleep(minutes(30)); // cool-down then requeue\n      return queueComment(accountId, videoUrl, text);\n    }\n    return { skipped: true, reason: 'post-button-never-enabled' };\n  }\n  throw e;\n}","preventionTips":["Rate-limit comments per account with generous cool-downs to avoid TikTok soft bans.","Keep comment text simple (plain ASCII, trimmed) so React state updates reliably.","Retry once before giving up; a single failed injection is often transient.","Periodically confirm Post button labels/attributes ('Post', '发布', '发送') against the live TikTok UI."],"tags":["tiktok","comment","selector","rate-limit"],"backgroundTag":"element-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}