X AutomationSAFE
Multi-Agent Harness for Production AI
Overview
Multi-Agent Harness for Production AI
e9251a22710aOBSERVED · 2026-10-07What it tells the agent
The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.
---
name: hive.x-automation
description: Read before automating X / Twitter with browser_* tools. Verified flows for post, reply, delete, search-and-engage, plus the Draft.js compose quirks that silently disable the send button. Includes the daily-reply and job-market-reply playbooks. Requires hive.browser-automation for the underlying screenshot + coordinate workflow. Verified 2026-04-11.
metadata:
author: hive
type: default-skill
version: "1.0"
verified: 2026-04-11
requires_skill: hive.browser-automation
---
# X / Twitter Automation
X uses **Draft.js** (the original Facebook rich-text editor) for the compose text area, which was the original canary for all the rich-text editor quirks the `browser-automation` skill now documents. Most of the site is otherwise stable — `data-testid` attributes have held up for years, the SPA is reasonably honest about what it renders, and shadow DOM is minimal. The hard parts are the composer, rate limiting, and the occasional anti-bot challenge.
**Always activate `browser-automation` first.** This skill assumes you already know about CSS-px coordinates, click-first typing, and `Input.insertText`. The guidance below is X-specific.
## Timing expectations
- `browser_navigate(wait_until="load")` returns in **1.3–1.6 s** on a warm cache.
- After navigation, **`sleep(2–3)`** for SPA hydration before querying selectors.
- Compose modal slide-in: **~1.5 s** after clicking reply / compose.
- First 1–2 characters typed into the compose editor **may be dropped** — see "Draft.js quirks" below.
## Verified selectors (2026-04-11)
| Target | Selector |
|---|---|
| Home nav link | `a[data-testid='AppTabBar_Home_Link']` |
| Explore nav link | `a[data-testid='AppTabBar_Explore_Link']` |
| Notifications | `a[data-testid='AppTabBar_Notifications_Link']` |
| Main search input | `input[data-testid='SearchBox_Search_Input']` |
| Compose text area | `[data-testid='tweetTextarea_0']` (Draft.js contenteditable) |
| Post / Tweet submit button | `[data-testid='tweetButton']` |
| Reply button (on feed / tweet detail) | `[data-testid='reply']` |
| Like button | `[data-testid='like']` |
| Retweet / repost button | `[data-testid='retweet']` |
| Caret (⋯) menu on a post | `[data-testid='caret']` |
| Confirmation sheet confirm button | `[data-testid='confirmationSheetConfirm']` |
| Tweet article wrapper | `article[data-testid='tweet']` |
| Close modal / composer | `[aria-label='Close']` or press `Escape` |
All of these are light-DOM `data-testid` attributes — `wait_for_selector` and `browser_type(selector=...)` work on them directly, no shadow piercing needed.
## Post new tweet flow
```
browser_navigate("https://x.com/home", wait_until="load")
sleep(3)
# Open the compose UI (click the post-new-tweet nav or use shortcut N)
browser_press("n") # keyboard shortcut — opens compose modal
sleep(1.5)
# Click the textarea to make sure Draft.js is in edit mode
ta_rect = browser_get_rect("[data-testid='tweetTextarea_0']")
browser_click_coordinate(ta_rect.cx, ta_rect.cy)
sleep(0.5)
# Type — browser_type handles Draft.js correctly now via Input.insertText
browser_type("[data-testid='tweetTextarea_0']", tweet_text)
sleep(1.0) # let Draft.js commit state
# Verify the Post button is enabled — never click blindly, Draft.js sometimes
# doesn't register the input even with a prior click.
state = browser_evaluate("""
(function(){
const btn = document.querySelector('[data-testid="tweetButton"]');
if (!btn) return {found: false};
return {
found: true,
disabled: btn.disabled || btn.getAttribute('aria-disabled') === 'true',
};
})();
""")
if state['found'] and not state['disabled']:
browser_click("[data-testid='tweetButton']")
sleep(2)
browser_press("Escape") # close any leftover modal
```
## Posting a tweet WITH an image
**Critical: NEVER click the photo button.** On `x.com/compose/post` the media button is a styled `<button>` that triggers Chrome's native OS file picker when clicked — that dialog is unreachable via CDP and will wedge the automation. Instead, set the file directly on the hidden `<input type='file'>` element using `browser_upload`:
```python
# 1. Open the compose modal as usual
browser_press("n")
sleep(1.5)
browser_click_coordinate(ta_rect.cx, ta_rect.cy)
sleep(0.5)
browser_type("[data-testid='tweetTextarea_0']", tweet_text)
# 2. Find the hidden file input X uses for media uploads.
# X's input is marked with data-testid='fileInput' and accepts
# image/*,video/*. It's hidden (display:none) but still mounted.
inputs = browser_evaluate("""
(function(){
return Array.from(document.querySelectorAll('input[type="file"]'))
.map(el => ({
testid: el.getAttribute('data-testid') || '',
accept: el.accept || '',
multiple: el.multiple,
}));
})();
""")
# Expect to see: [{testid: 'fileInput', accept: 'image/jpeg,...', multiple: true}]
# 3. Set the file WITHOUT opening any dialog
browser_upload(
selector="input[data-testid='fileInput']",
file_paths=["/absolute/path/to/photo.png"],
)
sleep(2) # X takes ~1-2s to show the preview thumbnail
# 4. Verify the preview rendered before posting — if not, the upload
# didn't land and Post button will fail.
preview = browser_evaluate("""
(function(){
// X renders uploaded media as an <img> with data-testid='attachments'
// (or similar) inside the composer.
const att = document.querySelector('[data-testid="attachments"] img');
return { hasPreview: !!att };
})();
""")
if not preview['hasPreview']:
raise Exception("Upload didn't render in composer — do NOT click Post")
# 5. Now click Post as usual
browser_click("[data-testid='tweetButton']")
sleep(3) # media upload + post takes longer than text-only
browser_press("Escape")
```
If you don't already have the image file on disk, write it first: `write_file("/tmp/x_upload.png", base64_bytes)` or copy from a known location. `browser_upload` requires an absolute file paTrust audit
SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | PASS |
| L1 | Static analysis of the code | NA |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | PASS |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- none-observed
- Network
- none-observed
- Shell
- none-observed
- Dependencies
- pinned
- Secrets in source
- none-found
Findings (1)
CLAUDE.md
Gates applied: no_behavioural_pass.
e9251a22710afull audit observations/trust-audit/skill/aden-hive__x-automation.json · Report an issue / request a re-scanAudit history
Every audit this skill has had.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-07 | e9251a22710a | SAFE | B | 89 | first audit |
Questions
What does the X Automation skill do?
Multi-Agent Harness for Production AI
Is X Automation safe to install?
The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.
What can X Automation access on my machine?
The audit observed no filesystem, network or shell use at all in its source.
How current is this page?
The grade is for one exact copy of the source (e9251a22710a), read on 2026-10-07. The repository is watched, and a new audit runs when it changes — this is the first audit.