---
disable-model-invocation: true
name: rodney
version: "1.1.0"
description: Drive a persistent headless Chrome from the CLI — one process keeps cookies and state across calls. Use when the user wants to scrape JS-rendered pages, screenshot, fill forms, export PDFs, or run browser tests in CI.
---

# Rodney — Chrome Automation

Rodney drives a persistent headless Chrome instance from the terminal. All commands share one long-running Chrome process — cookies, localStorage, and navigation state persist across invocations.

![Rodney architecture — short-lived CLI processes rendezvous via state.json through go-rod at one persistent headless Chrome](assets/rodney-architecture.png)

*How it fits together: every `rodney <cmd>` is a short-lived Go process that reconnects through `state.json` and the [go-rod](https://github.com/go-rod/rod) library to the same long-lived Chrome. Source of truth: [assets/rodney-architecture.html](assets/rodney-architecture.html) (PNG derived from it).*

## ⚠️ Usage: CLI Only — NOT an MCP Tool

Rodney is a **CLI tool**, not an MCP server. Use it via the **bash** tool only. Never call `mcp("rodney")`.

Every rodney session follows this pattern via bash:

```bash
rodney start                              # 1. Launch Chrome
rodney open <url>                         # 2. Navigate
rodney waitstable                         # 3. Wait for page to settle
# ... interact, scrape, screenshot ...     # 4. Do your work
rodney stop                               # 5. ALWAYS stop when done
```

**Important:**
- Call each command as a separate bash invocation (e.g. `rodney start`, then `rodney open <url>`, etc.)
- **Always `rodney stop`** when finished — otherwise Chrome runs forever
- Combine start → open → waitstable → work → stop in every workflow
- Other lifecycle commands: `rodney connect <host:port>` (attach to an already-running Chrome on a debug port), `rodney status` (browser info + active page)

> **`rodney --help` is the source of truth — use it to self-discover.** Rodney is an
> external dep whose feature set evolves. The command list below is only a **curated
> snapshot** and may be stale. **Always run `rodney --help` first** to see the actual
> installed commands, flags, and env vars, and drive your session from that output rather
> than trusting these docs. If a command errors or you suspect a newer flag exists,
> re-check `rodney --help`.

## Use the Latest Rodney

Rodney ships as release binaries built locally (not on PyPI). **Minimum version: see
[`MIN_VERSION`](MIN_VERSION)** (currently `0.12.1` — the about:blank panic fix). Older
binaries panic on `rodney open about:blank` with a bare Go panic (exit 1). The gate is
enforced by scripts, not prose: `install.sh`/`install.bat` read `MIN_VERSION` and fail
the install if the downloaded asset is older, and `tests/rodney/test.sh` fails the suite.

```bash
rodney --version          # must satisfy MIN_VERSION
```

If older — refresh via the skill's installer (downloads the latest release binary,
no Go toolchain needed):

```bash
bash skills/rodney/install.sh
rodney --version          # confirm the refresh
```

Or manually, from the release page (darwin/linux × amd64/arm64 tarballs at
<https://github.com/devskale/rodney/releases/latest>):

```bash
curl -fsSL https://github.com/devskale/rodney/releases/latest/download/rodney-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m).tar.gz | tar -xz
mv rodney ~/.local/bin/
```

Then run `rodney --help` to discover the current command set.

## Install

Preferred — the skill installer (downloads the platform release binary):

```bash
bash skills/rodney/install.sh
```

Fallback — build the Go binary from source (from the `skale` branch; requires
Go 1.21+ and Chrome/Chromium, set `ROD_CHROME_BIN` if Chrome isn't at the
default location):

```bash
git clone -b skale https://github.com/devskale/rodney.git
cd rodney
go build -ldflags="-s -w" -o ~/.local/bin/rodney .   # put it on your PATH
```

Verify with `rodney --version` against [`MIN_VERSION`](MIN_VERSION) — the skill requires
that version or newer, and the installers refuse to leave an older binary in place.

## Project Setup

To add rodney to a project, add this to the project's AGENTS.md:

```markdown
## Browser Automation

Use rodney for headless Chrome automation (scraping, screenshots, forms, PDFs, a11y, smoke tests).

### Setup
1. Install: `go build -o ~/.local/bin/rodney .` (from a clone of devskale/rodney)
2. Verify: `rodney start && rodney stop`
3. Link skill: `ln -s /path/to/skale-skills/skills/rodney .pi/skills/rodney`

### Usage
```bash
rodney start
rodney open https://example.com
rodney waitstable
rodney screenshot page.png
rodney stop
```
```

## Quick Start

```bash
rodney start                          # Launch headless Chrome
rodney start --show                   # Launch visible Chrome (for debugging)
rodney open https://example.com       # Navigate
rodney text "h1"                      # Extract text
rodney screenshot page.png            # Screenshot
rodney stop                           # Shut down
```

## When to Use: `surf` vs `rodney` vs `chrome-devtools-mcp`

These three overlap; pick by whether you need the user's **real session** or a **clean
isolated browser**:

- **Real session on macOS, zero setup** → **`surf`** (AppleScript into their visible Chrome).
- **Real session on any OS, or deep debug (console/network/perf)** → **`chrome-devtools-mcp`**.
- **Fresh isolated headless browser for scraping/forms/PDFs/a11y/CI** → **`rodney`** (this skill).

They compose — use `surf`/`chrome-devtools-mcp` on the user's real session, `rodney` for
isolated/headless jobs and CI. For the full agent-readable decision tree and feature
matrix, see **[docs/browser-use/which-browser-tool.md](../../docs/browser-use/which-browser-tool.md)**
(single source of truth).

## Commands

### Navigation & Waiting

```bash
rodney open <url>           # Navigate (auto-adds http://; about:/data:/blob:/javascript: never prefixed — v0.12.1)
rodney open <url> --reuse   # Switch to an existing page at that URL instead
                            #   of navigating the active page away (parallel
                            #   sessions converge on ONE page)
rodney back                 # Go back
rodney forward              # Go forward
rodney reload [--hard]      # Reload (bypass cache with --hard)
rodney wait <selector>      # Wait for element to appear
rodney waitload             # Wait for page load event
rodney waitstable           # Wait until DOM stops changing
rodney waitidle             # Wait for network idle
rodney sleep <seconds>      # Fixed delay
```

### Content Extraction

```bash
rodney title                        # Page title
rodney url                          # Current URL
rodney text <selector>              # Text content (one per match)
rodney html [selector]              # HTML (full page or element)
rodney attr <selector> <name>       # Attribute value
rodney js <expression>              # Evaluate JS, return result
```

### Screenshots & PDFs

```bash
rodney screenshot [-w N -h N] [file]         # Viewport screenshot
rodney screenshot-el <selector> [file]        # Element screenshot
rodney pdf [file]                             # Export as PDF
```

### Interaction

```bash
rodney click <selector>            # Click element
rodney input <selector> <text>     # Type into input (sets .value directly)
rodney type <text>                 # Real keyboard typing into focused element
                                   #   (fires key events — SPA validation reacts)
rodney press <key> [key ...]       # Real key events: enter, tab, ctrl+a, shift+tab
rodney scroll <x> <y> [--steps N]  # Scroll page by pixels (negative y = up)
rodney scroll-el <selector>        # Scroll element into view
rodney clear <selector>            # Clear input
rodney select <selector> <value>   # Select dropdown option
rodney submit <selector>           # Submit form
rodney hover <selector>            # Hover
rodney file <selector> <path>      # Set file on file input
rodney download <selector> [file]  # Download href/src target
```

### Cookies

```bash
rodney cookie-set <name> <value> [opts]   # Set a cookie (defaults to current page)
rodney cookie-get [name] [--json]         # Get cookie value by name, or all as JSON
rodney cookie-delete <name> [opts]        # Delete cookies by name
rodney cookie-clear [--domain <dom>]      # Clear all cookies (or one domain)
```

`cookie-set` options: `--domain <dom>`, `--url <url>`, `--path <path>`, `--secure`, `--httponly`, `--samesite <Strict|Lax|None>`, `--expires <unix>`.
`cookie-delete` options: `--domain <dom>`, `--url <url>`, `--path <path>`.

### Emulation

```bash
rodney ua <user-agent>              # Override browser user agent string
rodney timezone <timezone-id>       # Override timezone (e.g. "Asia/Tokyo")
rodney locale <locale>              # Override locale (e.g. "de-DE")
rodney geo --lat N --lon N          # Spoof geolocation coordinates
rodney media [--type T] [--feature name=value ...]  # Emulate media type/features
```

`media` examples: `rodney media --type print` (print media), `rodney media --feature prefers-color-scheme=dark`.

### Network Interception

```bash
rodney mock <pattern> <response> [--status N] [--type MIME] [--method M]  # Serve canned response for matching requests
rodney block <pattern> [--method M]                                        # Fail matching requests client-side
```

Both run as **persistent foreground commands** (until Ctrl+C) — other rodney commands in
separate shells drive the browser while the interception is active. `mock` serves a body
(or `-file=<path>`), `block` fails with `BlockedByClient`. See
[references/commands.md](references/commands.md) → *Network Interception* for the full
flag tables and examples.

### Video Recording

```bash
rodney start-video              # Start recording video of the active page
rodney stop-video [file]        # Stop and save (.gif default, .mp4 needs ffmpeg)
```

### Tabs

```bash
rodney pages                # List tabs (* marks active, t:<id> = stable target ID)
rodney pages --json         # Machine-readable: index, targetId, title, url, active
rodney page <index|t:id>    # Switch tab — t:<id> is drift-proof when parallel
                            #   sessions shift the indices
rodney newpage [url]        # Open new tab
rodney closepage [index|t:id]  # Close tab
rodney console              # Live console log stream (Ctrl+C stops)
rodney console-start        # Background collector → console.jsonl
rodney console [--clear]    # Read + empty the collector buffer
rodney console-stop         # Stop collector
rodney requests [--json]    # Network requests (live or collector-buffered)
rodney requests-start/stop  # Background request collector → requests.jsonl
rodney dialog [--dismiss]   # Arm JS-dialog handler BEFORE triggering
                            #   (accept default; --dismiss cancels; --text answers)
rodney screenshot --full    # Explicit full-page capture
```

### Assertions (exit 1 on failure)

```bash
rodney exists <selector>                        # Element exists?
rodney visible <selector>                       # Element visible?
rodney count <selector>                         # Count matches
rodney assert <expr> [expected] [-m msg]        # JS truthy or equality check
```

### Accessibility

```bash
rodney ax-tree [--depth N] [--json]             # Dump accessibility tree
rodney ax-find [--name N] [--role R] [--json]   # Find accessible nodes
rodney ax-node <selector> [--json]              # Element accessibility info
```

### Network interception (mock / block)

Intercept the browser's network to **mock** API responses or **block** requests — for
error-state testing, offline simulation, and deterministic scraping. These run as
**persistent foreground processes** (own shell, stop with Ctrl+C).

```bash
rodney mock "*api.example.com/users*" '{"id":1}' --type application/json   # canned response
rodney block "*.jpg" "*.gif"                                                # fail requests
```

**Full guide with patterns, flags, and use cases:**
**[references/network-interception.md](references/network-interception.md)**.

## Sessions

| Type | State | Flag |
|------|-------|------|
| Global | `~/.rodney/` | default |
| Local | `./.rodney/` | `--local` |
| Named | `~/.rodney-sessions/<name>/` | `--session <name>` |

Use `--local` for per-project isolation. Auto-detects local if `./.rodney/state.json` exists.

**Parallel sessions:** `rodney --session <name> <cmd>` gives each session its own
state — its own `active_page` and its own Chrome (a session's `stop` kills only
its own browser). Combine with `open --reuse` and `page t:<id>` so parallel
sessions converge on pages instead of duplicating and drifting. Each named
session starts its own headless Chrome (full isolation, more RAM); it does
NOT share your visible Chrome or your logged-in profile.

## Environment

| Variable | Default | Purpose |
|----------|---------|---------|
| `ROD_CHROME_BIN` | auto | Chrome binary path (go-rod standard) |
| `ROD_TIMEOUT` | `30` | Element query timeout (seconds, go-rod standard) |
| `RODNEY_HOME` | `~/.rodney` | Data directory |
| `RODNEY_ORPHAN_TTL_HOURS` | `24` | Orphan TTL: a session older than this is swept on the next `start` **even while its browser is still alive** (the hung-Chrome case — a live PID with a valid `state.json` used to be protected forever). `0` disables the age sweep. |
| `RODNEY_MAX_SESSIONS` | `6` | Cap on concurrent named sessions (`~/.rodney-sessions/*`). Beyond it, `start` refuses and names the sessions to stop. `0` disables the cap. |

## Gotchas

- **Always `rodney stop`** when done — otherwise a Chrome process lingers. The next `start` sweeps orphans (and anything older than `RODNEY_ORPHAN_TTL_HOURS`, default 24h), but "eventually collected" is not "cleaned up now": stop what you start.
- **`waitstable` is preferred** over `waitload` for SPAs and dynamic pages — `waitload` only fires on initial navigation, not on client-side renders.
- **`js` results are stringified** — arrays and objects come back as JSON strings. Pipe through `python3 -m json.tool` or use `--json` flags where available.
- **`js` takes a single string argument** — pass the whole expression as ONE quoted argument, including newlines: `rodney js '1 +\n2'` works. What fails is passing multiple separate args (`rodney js '1' '2'` → SyntaxError, only the first is wrapped). For complex logic, use an IIFE: `rodney js "(function(){ var els = document.querySelectorAll('.item'); return els[0].innerText; })()"`
- **`js` can't await async IIFEs** — `rodney js "(async () => { await … })()"` fails with `-32000 Promise was collected`: the pending promise is GC'd before it resolves, so staged actions (clicks, inputs) are silently lost. Split into two synchronous calls — stage (`window.__btn = …`) in one, act (`window.__btn.click()`) in the next — or schedule inside the page (`setTimeout`) instead of awaiting.
- **Selectors are CSS only** — no XPath. Use `rodney js` for complex queries.
- **`start` while already running is NOT a no-op** — it launches a *second* Chrome process (new PID, new debug URL) and overwrites `state.json`, orphaning the old Chrome (which keeps running). Check first with `rodney status`, or `rodney stop` before re-`start`. In scripts, guard with `rodney status || rodney start`.
- **Clean up stale/orphan processes** with the bundled utilities (below).
- **`open` auto-adds `http://`** — for `https://` URLs, include the scheme explicitly. Special URLs (`about:`, `data:`, `blob:`, `javascript:`) are never prefixed (v0.12.1) — `rodney open about:blank` works.
- **Exit codes**: 0 = success, 1 = assertion failed, 2 = error (bad args, timeout, no browser).
- **Heavy React apps** (booking sites, SPAs with autocomplete dropdowns) may timeout on `click`/`input`. Workaround: use the site's public API directly (most airlines, travel sites have one), or use `rodney js` to set values programmatically.
- **⚠️ The rod-managed Chromium (`~/.cache/rod/browser/`) is a frozen 2024 Tip-of-Tree dev snapshot, not stable** — go-rod pins revision 1321438 (Chromium 128.0.6568.**0**) and has shipped no update since July 2024. On newer distros this snapshot can be **partially broken**: `text`/`Element.Text()` returns `""`, `type`/`InsertText` never lands, and the HistoryEmbeddings AI feature can SIGABRT the whole browser (see rodney#4, rodney#3 / diskfull 2026-10-07). **Since v0.12.0 rodney auto-prefers the system browser** (`launcher.LookPath()`: Chrome/Chromium/Edge) over the snapshot — `ROD_CHROME_BIN` is only needed as an explicit override or on boxes without a system browser. On older binaries: point `ROD_CHROME_BIN` at your system Chrome (`/opt/google/chrome/chrome` on Linux). If `text` hangs or returns empty on a fresh page, suspect the snapshot first (`rodney doctor` shows which browser is in use).

## Process visibility & cleanup

Rodney launches its own **headless Chromium** (downloaded via go-rod into
`~/.cache/rod/browser/...`), identified by its `--user-data-dir=*.rodney` — not by
"chrome" in the name. Two bundled utilities give you process visibility and zombie
prevention (they live in `skills/rodney/scripts/`, symlinked as `rodney-ps` /
`rodney-cleanup`):

```bash
rodney-ps --json           # managed vs orphan Chromium processes (machine-readable)
rodney-cleanup --clean     # remove stale state + kill orphans + purge old /tmp dirs
rodney-sessions            # list named sessions: pid, status, pages, age, size
rodney-sessions --stop-all # stop every RUNNING --session (graceful)
rodney-sessions --clean [--age 24h]  # remove dead session dirs (crash leftovers)
rodney-cleanup --json      # quick health check: is a browser running? how many orphans?
```

- **Check before `start`**: `rodney status || rodney start` avoids orphaning a live browser.
- **`--clean` is non-interactive** and only ever touches rodney-owned processes
  (`.rodney` user-data-dir) and `/tmp/chrome-*` dirs older than 24h — never your real Chrome.
- Handles both **global** (`~/.rodney/`) and **local** (`./.rodney/`) sessions.

## Retry patterns

Rodney exit codes: `0` = success, `1` = assertion/check failed (retry candidate),
`2` = error (bad args, timeout, no browser — **fix, don't retry**). For flaky
timing/network steps, retry in a shell loop instead of re-inspecting each time:

```bash
for i in 1 2 3; do
    rodney waitstable && rodney click "#submit" && break
    sleep 2
 done
```

Adapt strategy after 3+ same failures, escalate after 5+. Full decision matrix:
**[references/TROUBLESHOOTING.md](references/TROUBLESHOOTING.md)**.

## References

- **Tiered help — THE reference, fetched live from the CLI.** The binary carries its own registry; fetch it at runtime instead of trusting static docs:
  1. `rodney --help` — grouped overview of every command, flag, env var
  2. `rodney help <command>` — that command's usage, flags, and examples (canonical)
  3. `rodney help --json` — the full registry, machine-readable (83 commands)
  
  Run these first to discover the installed feature set — the binary updates independently of these docs (the installer pulls the latest release), so it may know commands this skill has never seen. When a command here doesn't behave as documented, or you need a capability the docs lack: trust the binary's help over these docs. New commands found via `--help` must surface into `references/commands.md` (the suite's drift check fails until documented).
- **[references/registry.md](references/registry.md)** — the binary's full command registry, **generated** (`scripts/gen-registry.sh --write`), never hand-edited: every command's usage, flags, examples, grouped. Byte-identical to `rodney help --json` by construction; regenerate after a binary update (the suite fails if stale).
- **[references/commands.md](references/commands.md)** — curated quick reference: one-liners per command plus gotchas, patterns, and cross-command notes the binary can't know about itself. Read this for the *why/when*, the registry for the *what/how*.
- **[references/examples.md](references/examples.md)** — Ready-to-use workflow scripts for scraping, form filling, smoke tests, and accessibility audits.
- **[references/debugging.md](references/debugging.md)** — Non-obvious debugging patterns: screenshot time-series, form validation checks, exit code chaining, and visible-mode debugging.
- **[references/network-interception.md](references/network-interception.md)** — Network mocking and blocking (`mock`/`block`): patterns, flags, and use cases for error-state/offline/deterministic testing.
- **[references/dev-workflow.md](references/dev-workflow.md)** — Dev loop: reload-assess-iterate, page inspection without screenshots, DOM structure, accessibility tree, layout queries.
- **[references/TROUBLESHOOTING.md](references/TROUBLESHOOTING.md)** — Exit codes, failure scenarios, stall detection, retry-vs-adapt decision matrix, cost-optimized retry.
- **[scripts/README.md](scripts/README.md)** — Process-management utilities (`rodney-ps` / `rodney-cleanup`): why the `.rodney` user-data-dir filter matters, install, modes.
