Verifying AI Agent Posts on X: From 83 False Wins to 94% Real Success
An AI agent for automated comments on X reported 83 successful posts, but only 16 actually appeared. The gap came from incomplete verification: clicking Reply doesn't guarantee the content shows up on the platform. X's spam filters quietly drop posts without any alerts.
How Posting Works via Browser
The agent uses Puppeteer CDP to control a real browser. It opens the thread, enters the text, and clicks Reply. The first version of the function only checked the DOM event:
const button = await page.$('[data-testid="tweetButton"]');
if (button) {
const disabled = await button.evaluate(el =>
el.getAttribute('aria-disabled') === 'true'
);
if (!disabled) {
await button.click();
return true; // <-- problem: early success
}
}
return false;
Returning true marked it as a success at the DOM level, without platform confirmation. The database piled up records, and the dashboard glowed green.
X doesn't signal rejections: the field clears, the URL stays the same. Rate limits or content policies kick in silently, leaving the browser under the illusion of success.
Issues with Post IDs
For tracking, the agent pulled the ID from page.url() after clicking:
const tweetId = page.url().split('/status/')[1];
X doesn't redirect to the new post—the URL remains the parent's. All 83 records got the same parent_tweet_id, hiding the issue behind seemingly valid data.
The problem surfaced manually via X analytics: 83 reported vs. 16 real replies.
Root Cause: Gap Between DOM and Platform
DOM: button.click() → success ✓
Browser: event fired → success ✓
X Platform: content dropped → ??? (silence)
Database: recorded as "posted" → success ✓
Dashboard: 83 engagements → great ✓
Reality: 16 actual posts
Monitoring stopped at the browser. Platform filters are a black box.
Solution: Post-Verification with Page Reload
The new verifyReplyPosted function waits for rendering, reloads the page, and scans the DOM for the text:
export async function verifyReplyPosted(
page: Page,
replyText: string,
parentTweetId: string,
): Promise<{ verified: boolean; replyTweetId: string | null }> {
await new Promise(r => setTimeout(r, 3000));
await page.reload({ waitUntil: "domcontentloaded" });
await new Promise(r => setTimeout(r, 5000));
await page.evaluate("window.scrollBy(0, 1000)");
await new Promise(r => setTimeout(r, 2000));
const result = await page.evaluate(`
(function() {
var snippet = '${safe}'; // first 30 chars
var articles = document.querySelectorAll(
'article[data-testid="tweet"]'
);
for (var i = 0; i < articles.length; i++) {
var textEl = articles[i].querySelector(
'[data-testid="tweetText"]'
);
if (!textEl) continue;
var text = (textEl.innerText || '').slice(0, 60);
if (text.indexOf(snippet.slice(0, 30)) !== -1) {
var links = articles[i].querySelectorAll(
'a[href*="/status/"]'
);
return { verified: true, replyTweetId: realId };
}
}
return { verified: false, replyTweetId: null };
})()
`);
return result;
}
- Reload ensures a fresh thread without cache.
- Searches via
article[data-testid="tweet"]and[data-testid="tweetText"]. - ID pulled from
hreflinks inside the article. - Only
verified: truegets logged to the database.
Why This Approach Works Anywhere
This isn't unique to X. Platforms (social media, forms, payments, deployments) quietly reject content.
Key verification principles:
- Mimic a real user: reload + scroll for lazy loading.
- Hunt for a marker (text snippet) in the target DOM.
- Pull IDs from actual elements, not URLs or events.
- Skip logging without proof.
- Log failures in real time.
Key Takeaways
- DOM events ≠ platform success: A click doesn't guarantee posting.
- Silent drops: Spam filters operate without alerts to thwart workarounds.
- DOM-scan verification: Reload + text/ID search.
- Post-fix metrics: 94% verified success rate, failures visible instantly.
- Broad use: Any automated posting/submission needs user-like checks.
After implementation, the agent tracks real posts, the dashboard shows the truth. The method scales to other platforms with similar filters.
— Editorial Team
No comments yet.