This is a battle‑tested protocol for running browser automation in OpenClaw without collisions between cron jobs and ad‑hoc/manual runs.
If you run X automation (mentions/search/posting), you will get:
- wrong-tab actions
- stale refs
- “tab not found”
- ghost posts (no toast)
- accidental standalone posts instead of replies
This protocol reduces all of that.
- Single writer: only one process may drive the managed browser at a time.
- Single tab: one page tab only (compose + verify + navigate back). Extra tabs are risk.
- Thread‑safe replies: never free‑post when intent is reply; require machine verification.
- Backoff + skip: if busy, wait briefly then skip, rather than interleave.
Use a shared lock file (JSON) that all browser users (cron + manual) respect.
Lock file (example):
/Users/gwbox/.openclaw/workspace/memory/cron_browser_mutex.json
{ "label": "x_mentions_hourly", "startedAtMs": 1730000000000 }python3 - <<'PY'
import json, time
LOCK='/Users/gwbox/.openclaw/workspace/memory/cron_browser_mutex.json'
json.dump({'label':'manual_<task_name>','startedAtMs':int(time.time()*1000)}, open(LOCK,'w'))
print('LOCK_OK')
PYrm -f /Users/gwbox/.openclaw/workspace/memory/cron_browser_mutex.jsonRule: If you can’t hold the mutex for the entire run, don’t start the run.
If mutex is held by another process:
- wait 60s, retry
- wait 120s, retry
- wait 240s, retry
- if still held: SKIP this run (don’t touch the browser)
If lock is older than 5 minutes:
- treat as stuck
- restart the managed browser once
- take the lock
Immediately after acquiring the mutex:
- List tabs
- Close extra page tabs, keep one
- Use that one tab for:
- composer
- submit
- toast View
- verification
- navigate back
Why: a lot of OpenClaw browser flake comes from targetId drift when multiple tabs exist.
Prefer:
https://x.com/intent/tweet?in_reply_to=<tweetId>
Before clicking Reply:
- fresh snapshot
- confirm “Replying to …” context exists
- confirm the primary button says Reply and is enabled
- confirm textbox contains intended text
After clicking Reply:
- require toast with View link
- open View
- machine‑verify threading:
- DOM contains any
a[href*="/status/<parentTweetId>"]
- DOM contains any
If verification fails:
- do not log state
- restart browser once and retry once
- otherwise stop (avoid standalone spam)
- stale ref / unknown ref: page changed → take a fresh snapshot, re‑select refs
- browser control timeout: restart browser once
- 3 consecutive failures on same goal: stop
Do not brute force. Thrash creates wrong posts.
Drop these blocks at the top of any cron payload that uses the browser:
- MUTEX WAIT / BACKOFF
- SINGLE‑TAB GUARD
- THREAD‑SAFE REPLY WORKFLOW
And make your workflows “read/write split”: collect targets first, then post.
import json, time
LOCK='.../cron_browser_mutex.json'
now=int(time.time()*1000)
try:
st=json.load(open(LOCK))
age=now-int(st.get('startedAtMs',0))
if age < 300000:
# locked and fresh
exit(3)
except FileNotFoundError:
passUse OpenClaw browser tooling to list tabs and close all but one page tab.
Most automation failures aren’t “selectors” — they’re concurrency + state drift. The mutex + single‑tab policy turns a flaky system into a mostly deterministic one.
If you adopt this protocol, you’ll ship more replies with fewer disasters.