# X Reply Posting

> Post replies to tweets on X (Twitter) via Chrome browser automation. Handles textarea activation, text insertion, keyboard submission, and verification. Use when posting replies on x.com through Claude Code.

- Skill: `blockchainhb/x-reply-posting` (Agent Skill)
- Install (CLI): `npx skillmds@latest add blockchainhb/x-reply-posting`
- Raw SKILL.md: https://api.skillmd.com/api/skills/blockchainhb/x-reply-posting/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: blockchainhb (https://skillmd.com/u/blockchainhb)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/blockchainhb/x-reply-posting

---


# X Reply Posting (Browser Automation)

Post replies to tweets on x.com using Chrome MCP browser tools. Uses the inline reply box with keyboard submit — the most reliable method.

## Prerequisites

- Chrome browser open with X/Twitter logged in
- Chrome MCP extension connected (`mcp__claude-in-chrome__*` tools available)

Verify Chrome MCP is available:
```
mcp__claude-in-chrome__tabs_context_mcp
```

If it errors: tell the user "Chrome MCP isn't connected. Open Chrome with the Claude extension active."

---

## Posting Flow (Per Reply)

### Step 1: Navigate to the Tweet

```
mcp__claude-in-chrome__navigate to https://x.com/{handle}/status/{tweet_id}
```

Wait for page load.

### Step 2: Check for Duplicate (Optional)

Before posting, verify you haven't already replied. Replace `'YOUR_HANDLE'` with the user's X handle:

```javascript
[...document.querySelectorAll('[data-testid="tweet"]')].some(t =>
  (t.querySelector('[data-testid="User-Name"]')?.textContent || '').toLowerCase().includes('YOUR_HANDLE')
);
```

If `true`, skip this tweet.

### Step 3: Click the Inline Textarea

Get the textarea position dynamically, then click it:

```javascript
const textarea = document.querySelector('[data-testid="tweetTextarea_0"]');
const rect = textarea.getBoundingClientRect();
JSON.stringify({ x: Math.round(rect.x + rect.width / 2), y: Math.round(rect.y + rect.height / 2) });
```

Use `mcp__claude-in-chrome__computer` to `left_click` at the returned `(x, y)` coordinates.

**This physical click is required.** JS `focus()` alone does not reliably activate the contenteditable div.

### Step 4: Insert Text and Submit

```javascript
const textarea = document.querySelector('[data-testid="tweetTextarea_0"]');
textarea?.focus();
document.execCommand('insertText', false, 'REPLY_TEXT_HERE');
document.querySelector('[data-testid="tweetTextarea_0"]')?.textContent || '';
```

Verify the returned text is non-empty. If empty, repeat Step 3 (click textarea) and retry once.

Then submit with keyboard shortcut:

```
mcp__claude-in-chrome__computer → key → cmd+Return
```

### Step 5: Verify

```javascript
const found = [...document.querySelectorAll('[data-testid="tweet"]')].filter(t => {
  const text = t.querySelector('[data-testid="tweetText"]')?.textContent || '';
  return text.includes('FIRST_FEW_WORDS_OF_REPLY');
});
found.length > 0;
```

If `true`, reply is confirmed. If `false`, retry from Step 3 once.

---

## Critical Rules

**Use the inline reply box.** The inline reply box at the bottom of a tweet page works. No need to open the compose modal.

**Never use `computer` `type` for reply text.** Use `execCommand('insertText')` via `javascript_tool`. This properly triggers React's state updates on the contenteditable div.

**Never use `form_input` tool.** The tweet textarea is a contenteditable div, not a form input. `form_input` will not work.

**Never rely on JS `focus()` alone.** Always visually click the textarea with `computer` tool first (at coordinates from `getBoundingClientRect`), then use `execCommand('insertText')`.

**Always find textarea coordinates dynamically.** The inline reply box position varies with tweet length and media. Use `getBoundingClientRect()` every time — never hardcode coordinates.

**Submit with `cmd+Return`.** This is the most reliable submit method. Works regardless of button position or React event handling.

---

## Handling 404s

If a tweet URL returns "this page doesn't exist":

```
navigate to https://x.com/search?q=from:{handle} {first_few_words}&f=live
```

Find the tweet in search results, extract the URL from `time?.closest('a')?.href`, and retry.

---

## Why Not Use the API?

X's CreateTweet GraphQL API requires an `x-client-transaction-id` header generated by an obfuscated client-side script (`ondemand.s` chunk) that uses DOM animation timing data. This is not practical to reverse-engineer. The browser automation method works reliably.

---

## Troubleshooting

| Issue | Fix |
|-------|-----|
| Textarea not found | Page hasn't loaded — wait longer before extraction |
| Text insertion fails | Click didn't activate textarea — repeat Step 3 click |
| Submit doesn't work | Verify `cmd+Return` (macOS) or `ctrl+Return` (Windows/Linux) |
| Reply not appearing | Check Step 5 verification — may need page refresh |
| "Detached while handling" | Chrome MCP timeout — check if reply posted before retrying |
| 404 on tweet URL | Use `rest_id` not `legacy.id_str` if extracting from API (see x-list-extraction skill) |

---

## Quick Reference: Full Flow

```
1. navigate → tweet URL
2. javascript_tool → get textarea coordinates (getBoundingClientRect)
3. computer → left_click at (x, y)
4. javascript_tool → execCommand('insertText', false, replyText)
5. computer → key → cmd+Return
6. javascript_tool → verify reply appears in DOM
```

---

*Tested Feb 2026. X can change DOM structure — if posting fails, inspect the reply box in DevTools to find updated selectors.*

