Configuration
Most settings are written for you by the Settings dialog. Press ctrl+a,
then ,. This page is for when you want to edit them by hand.
Saved repository lookups and per-repository init overrides match equivalent Windows path spellings. A repository saved with backslashes can be selected or configured using Git-style forward slashes without creating a second entry.
Where things live
| Path | What | Written by |
|---|---|---|
~/.config/rove/state.json | All your preferences, as flat JSON | Rove (Settings, CLI); yours to hand-edit |
~/.rove/secrets.json | Credentials Rove holds for you (today: the tier classifier's API key) | Settings → Auto routing; not part of state.json, and never printed |
~/.rove/themes/*.json | Installed themes | rove theme add, or drop files in |
~/.rove/settings/keybindings.yaml | Keybinding overrides | You only |
<repo>/.rove/init.sh + init-prompt.md | Per-repo worktree setup | You (committed to the repo) |
<repo>/.rove/pr-instructions.md | Per-repo PR action prompt | You (committed to the repo) |
<repo>/.rove/clone-dirs | Per-repo list of ignored directories to clone into new worktrees | You (committed to the repo) |
Setting ROVE_HOME_DIR changes the home beneath all these paths. Only ROVE_*
environment variables are read. New runtime files use canonical names; a live
pre-rename PTY host can still be attached until it exits or rove reset runs.
See runtime state.
Runtime path overrides
ROVE_HOME_DIR already decides where the daemon and the PTY host put their
socket and pidfile, so most people never touch these. Four variables move one
file each, for the case the home cannot cover: running a second Rove beside
the one you use, without the two finding each other.
| Variable | Moves |
|---|---|
ROVE_DAEMON_SOCKET_PATH | The socket the daemon listens on, and clients connect to |
ROVE_DAEMON_PID_PATH | The daemon's pidfile (what rove daemon stop reads) |
ROVE_PTY_SOCKET_PATH | The PTY host's socket — a named pipe on Windows |
ROVE_PTY_PID_PATH | The PTY host's pidfile |
Each has a ROVE_-prefixed fallback (ROVE_DAEMON_SOCKET_PATH, and so on);
when both spellings are set, the ROVE_ one wins. Unset ones stay derived
from the home.
Set them as a group, in the same command as ROVE_HOME_DIR. Isolating the
home alone still leaves the two processes on the paths they were given, and a
half-isolated instance either refuses to start (already served by the daemon on …) or, worse, drives the terminals of the instance you are using:
env ROVE_HOME_DIR=/tmp/scratch-home \
ROVE_DAEMON_SOCKET_PATH=/tmp/scratch-home/daemon.sock \
ROVE_DAEMON_PID_PATH=/tmp/scratch-home/daemon.pid \
ROVE_PTY_SOCKET_PATH=/tmp/scratch-home/pty.sock \
ROVE_PTY_PID_PATH=/tmp/scratch-home/pty.pid \
rove daemon restartA socket path that is too long for the platform is shortened automatically; a pidfile path is used as given. See Troubleshooting for what a half-applied override looks like from the outside.
Editing settings
rove config # open state.json in your editor
rove config --path # just print the pathrove uses your configured editor (editor.kind below) unless it is auto
(the default), which honors $VISUAL / $EDITOR, then the first installed
of nvim, vim, emacs, nano.
Restart Rove to apply a hand edit everywhere.
Hand-editing is safe. Unknown keys are ignored and bad values fall back to
defaults, so a typo can't wedge the app; worst case a preference resets. If
the file becomes invalid JSON, Rove renames it to
state.json.corrupt-<timestamp> and starts fresh rather than deleting it.
Rove serializes each complete read, mutation, and atomic write with the
state-file lock, so concurrent processes changing different keys preserve
both changes. A whole-state reset takes the same lock and intentionally
replaces all keys. Hand edits do not participate in this lock; finish editing
before changing settings in another Rove process.
Corruption backup also takes the write lock and re-reads the file before renaming it. A reader that cannot immediately acquire the lock returns defaults without moving the file, allowing an active writer to finish its repair. Writers wait up to five seconds for contention and then report failure. The UI retains unsuccessful dirty-key patches for its next flush; CLI writes report the error instead of claiming the setting was saved.
Settings reference
Keys not listed here are internal UI state (saved repos, tab layouts) that
happen to share the file. Three exceptions worth knowing: repoConfigs is what
rove repo set writes (a map of git toplevel → {initScript, initPrompt},
see Per-repo init); lastSelectedVendor is the legacy
engine fallback below defaultVendor; and externalWorktreeSync is not a
setting you configure but a cleanup marker Rove writes — it records where the
retired worktree-sync hook was once installed so the next launch (or
rove hook cleanup) can remove it, flipping to "off" once cleaned.
Appearance
Settings → General groups theme, mode, transparency, focus accent, split style,
folded rail, tab row height, working border, task colors, state glyphs, colorblind diff colours, and running title below a workspace preview. Each row opens a selection
list with the same preview above it. Use j/k or arrows to preview a choice,
enter to save it, or esc to cancel. Previewing never changes saved settings.
Clicking an option saves it immediately. The preview uses sample tasks and
files; it does not show your sessions.
| Key | Type | Default | What it does |
|---|---|---|---|
activeTheme | theme name | "claude" | See Themes |
themeMode | dark | light | auto | "dark" | Which half of a theme's { dark, light } colors Rove draws. auto asks the terminal for its background (OSC 11) and switches when the terminal reports an appearance change, so a terminal that follows the OS light/dark setting takes Rove with it. A terminal that never answers leaves auto on dark. A theme without light colors looks the same in both modes |
transparentBackground | boolean | true, false on Windows | Let the terminal background show through. In transparent mode Rove detects the terminal's actual background (OSC 11) and adjusts body, muted, and host-backed warning text to stay readable on it. Warning text on opaque dialogs and controls keeps the theme color. No setting is needed. Windows starts opaque because Windows Terminal ships acrylic and background images on by default, and a transparent Rove has no opaque surface to scrub stale glyphs against — set it to true to turn transparency on there, and a value you have already chosen is never overwritten |
focusAccent | primary | success | info | primary | Color of the focused-pane indicator |
appearance.splitStyle | box | line | rail | rule | box | box frames each split; line is the minimal tmux-style look; rail marks each split with a left accent bar; rule draws a top rule over each split with its name at the right end. The focused split's edge takes the focus accent |
sidebar.tabRowHeight | 1 | 2 | 1 | 2 gives each agent tab a second line naming the running engine and, when the task pins a reasoning level that engine declares, the level behind a fill glyph that runs ○ (lowest) to ◉ (highest) along the engine's own level list |
appearance.workingBorder | flow | still | flow | While the selected task's engine is running, flow runs a colour gradient around the workspace pane's border (derived from the theme accent) and names the task on its top edge; still keeps the plain focus border |
appearance.taskColors | on | off | on | on gives every task its own hue, picked from the task id so it stays the same across restarts: the flowing working border runs in the selected task's hue, and each task's sidebar rows carry a one-cell mark in it. The hue takes its lightness and strength from the theme accent and keeps away from the success, warning and error hues. off uses the theme accent everywhere |
appearance.glyphSet | braille | starburst | ascii | braille | The marks the task rail, tab strip, Inbox and PR chip draw: braille spins ⠋⠙⠹… beside ○ ● ? !; starburst spins ✻✼❉❊✺✹✸✶ beside ✧ ✦ ? ❢ and needs a font with the Dingbats block (without one, macOS falls back to glyphs of different widths and the rows jitter); ascii uses | / - \ beside o * ? ! for terminals and fonts without Unicode symbols |
appearance.colorblind | off | on | off | on turns the theme's added colour 60° around the hue wheel toward blue, at lower chroma, so added and removed stop being a red/green pair. Applies to the diff view, the Files pane's A/D letters and +N/-M counts, and the sidebar's +N −M chips; removed keeps the theme colour |
appearance.runningTitle | shimmer | still | shimmer | While a tab's engine is running, shimmer sweeps a light band (theme muted text toward the primary colour) across its title in the sidebar at a fixed 30 cells per second; still keeps the plain muted title |
locale | en | zh | en | UI language |
hints.keyboard.enabled | boolean | true | Keyboard discoverability hints |
hints.keyboard.prefixTapPresentation | local | guide | local | One tap of the prefix key always opens the full keyboard guide. This picks what comes with it: local also shows shortcut badges beside the clickable controls already on screen, guide hides those badges |
Turning keyboard hints back on relights the first-use pane hints you'd already dismissed.
Editor
editor.kind and editor.customCommand control the file tree's enter
action and rove config. Opening an entire worktree (o in the sidebar or
ctrl+a o) uses the separate GUI/workspace opener described below.
| Key | Type | Default | What it does |
|---|---|---|---|
editor.kind | auto | vim | nvim | nano | emacs | custom | auto | auto honors $VISUAL/$EDITOR, then auto-detects |
editor.customCommand | string | unset | Command for custom, e.g. code -w |
In editor.customCommand, {file} is replaced by the quoted file path. Without
it, the path is appended.
Set ROVE_OPEN_EDITOR to choose the GUI editor for a whole worktree, for
example ROVE_OPEN_EDITOR=zed. Without that variable,
Rove tries the code, cursor, windsurf, and zed CLIs in that order,
then the platform opener. These variables do not change the file tree's
per-file TTY editor.
The Files pane watches the worktree so edits appear without a keypress. Set
ROVE_FILETREE_WATCH=0 to turn that watcher off — worth doing on a repo large
enough that a recursive watcher costs more than the staleness it removes. With
it off, r is the only thing that repopulates the list.
Engines
| Key | Type | Default | What it does |
|---|---|---|---|
defaultVendor | engine id | "claude" | Default engine for new tasks |
engineCommand.<id> | string | built-in | Launch command, e.g. "engineCommand.claude": "claude --model opus" |
engineName.<id> | string | built-in | Display name |
customEngineIds | string[] | [] | Your own engines; see Custom engines |
engineProtocol.<id> | built-in engine id | unset | Adapter a custom engine borrows; see Custom engines |
lastActiveVendor.<repo> | engine id | unset | Per-project last used; outranks defaultVendor. Written by Rove |
autoRouting.<tier>.engine | engine id | claude for all three | What the swift / standard / deep depth launches. Set from Settings → Auto routing; an empty string switches auto routing off (no tier is guessed) |
autoRouting.<tier>.model | string | sonnet / opus / fable | Model for that depth, in the engine's own spelling; empty = the engine's default |
autoRouting.<tier>.effort | string | unset | Reasoning level for that depth, one the engine declares; empty = the engine's default |
autoRouting.classifier | off | jev | an http(s):// URL | off | Who picks the tier for rove api add --tier auto. See below — anything else, a typo included, reads as off |
autoRouting.classifierThreshold | number, 0–1 | 0.5 | Confidence below which no tier is picked. A value outside the range is refused and the default used |
autoRouting.classifierTimeoutMs | number, 200–60000 | 4000 | How long to wait before giving up on the classifier. Outside the range, the default |
autoRouting.classifierModel | string | jev-latest | Model id for jev. Pin a version (e.g. jev-1.13.0) to stop a silent upgrade |
autoRouting.classifierEndpoint | string | unset | The custom endpoint Settings remembers while the classifier points elsewhere. Not read by the classifier — autoRouting.classifier is |
autoRouting.classifierKeyEnv | string | TYPESAFE_API_KEY | Environment variable holding jev's key. The token itself never goes in state.json |
autoRouting.classifierCustomKeyEnv | string | unset | Environment variable holding a custom endpoint's key. Unset = no Authorization header is sent |
Launch commands are parsed shell-ish, so quotes group arguments. Clear both
engineName.<id> and engineCommand.<id> to reset an engine to its default.
These three keys were called autoEffort.<tier>.* up to v0.9.220, and the
feature was called Auto effort. Rove moves the old keys to the new names once,
at the first launch after upgrading, and deletes the old ones — a table you had
retargeted keeps launching exactly what it launched before, and there is
nothing to do by hand. If you had both spellings in the file (an older Rove run
in between, say), the autoRouting.* value is the one kept.
The tier classifier
autoRouting.classifier is off, and while it is off nothing leaves your
machine. Switching it on means one thing you should decide deliberately:
Your prompt is sent to a third party that is not your engine vendor. With
jev, the first message of the task — trimmed to 1,200 characters — is POSTed to TypeSafe System One (https://api.typesafe.ai/v1/systemone). That is a different company from whoever runs the engine you picked, and it sees the text before the engine does.
It answers one question — how much of the PROCEDURE the prompt leaves for the
model to work out — and maps the answer onto swift / standard / deep:
procedure given is swift, goal given but not the procedure is standard,
and a goal that still has to be found is deep.
{
"autoRouting.classifier": "jev", // off by default
"autoRouting.classifierThreshold": 0.5, // below this, no tier is picked
"autoRouting.classifierModel": "jev-1.13.0"
}export TYPESAFE_API_KEY=... # keys: https://console.typesafe.ai/keys
rove api add --repo ~/code/app --tier auto --prompt "there's a memory leak somewhere"Settings → Auto routing carries all of this as rows — Classifier (off /
jev / custom), Endpoint, Confidence floor, API key — with the
data-flow sentence above them in every mode, and a line saying where the key
is coming from, which is the usual reason a switched-on classifier appears to
do nothing. Everything below is the same settings by hand.
Where the key lives
Two places, in this order (and $TYPESAFE_API_KEY below means whichever
variable the current mode reads):
- The variable in the environment — wins whenever it is set, so a one-off
TYPESAFE_API_KEY=… rove …and a CI secret behave the way you would expect. An empty value counts as unset, not as "deliberately blank". ~/.rove/secrets.json— what Settings → Auto routing → API key writes. It holds nothing but secrets and is deliberately NOTstate.json: that file is opened byrove config, hand-edited, and pasted whole into bug reports, which is no place for a live key.
The stored key is the only way a running TUI can have one: it is a long-lived
process, so an export typed after it started never reaches it. Settings
shows the last four characters of a stored key and never the key; submitting
the field empty clears it, and clearing the last key removes the file. A
secrets.json that no longer parses is renamed to
secrets.json.corrupt-<timestamp> on the next save rather than overwritten,
so the keys still inside it can be recovered by hand. Rename
the variable with autoRouting.classifierKeyEnv and both places follow the
new name.
On Windows,
0600is not what protects that file. Rove writes it with owner-only permissions, and on macOS and Linux that is enforced. Node maps POSIX mode bits onto the read-only attribute on Windows; who may open the file is decided by the ACL it inherits from your user profile. That is the same protection every other per-user file there has, and it is weaker than the guarantee on the other two platforms — if that matters for your threat model, keep the key in the environment instead.
Point autoRouting.classifier at your own endpoint instead and Rove POSTs
{"text": "…"} and expects {"tier": "swift|standard|deep", "confidence": 0.0–1.0} back — that is the whole contract, so an endpoint you host (a local
model, a rule, a lookup) is a ten-line program.
http:// is accepted only for a loopback address (127.0.0.1,
localhost, ::1), because a classifier you run on your own machine is the
whole point of the custom option and nothing leaves the host. To anywhere
else, plain http would carry the task's first 1,200 characters — and your
bearer token, once you name one — in cleartext to a host anyone on the path
can impersonate, so it is refused and .tierAuto says so.
The two modes read different key settings, and that is deliberate. jev
reads autoRouting.classifierKeyEnv; a custom endpoint reads
autoRouting.classifierCustomKeyEnv, which has no default, so a custom
endpoint gets no Authorization header until you name one. A single
shared variable would leak in both directions across a mode switch: a name
chosen for your own endpoint would send that credential to TypeSafe the
moment the setting changed to jev, and TypeSafe's token would go to your
endpoint the moment it changed back. Neither is a mistake you could watch
yourself make.
Nothing here can fail a create. Off, no key, no network, a timeout, a
malformed answer, a refused endpoint, or a confidence under the threshold all
mean the same thing: the task is created with the engine fields it would have
had anyway, and rove api add reports what happened in .tierAuto. A pick
below the threshold is dropped on purpose — a wrong pre-fill costs more than
no pre-fill, because someone has to notice it before they can undo it.
The judgement Rove sends is the one that was measured: 73.0% on 94
human-labelled prompts against a 48.9% floor, with deep recall 17/20. It is
generated from design/auto-routing/ rather than
written in code, so the text a reviewer reads is the text on the wire. What
the numbers mean and what else was tried:
design/auto-routing-classifier.md
and
design/auto-routing-classifier-interface.md.
Terminal and tabs
| Key | Type | Default | What it does |
|---|---|---|---|
terminal.scrollbackRows | number | 1000 | History per embedded terminal. Clamped 100–100,000 |
chat.tabStrip.mode | always | multipleOnly | never | never | Horizontal chat tab strip |
The tab strip is off by default: the sidebar tree already lists every tab and
marks the active one, so the strip spends a row of the content pane saying
what the tree says for free. always shows it, multipleOnly shows it only
once a task has more than one tab. (An older
chat.tabStrip.hideSingle boolean still works if you set it before
chat.tabStrip.mode existed; writing the new key retires it.)
Scrollback changes apply to terminals started after the change; live ones keep the buffer they were born with.
Notifications
All three default to on.
| Key | Type | What it does |
|---|---|---|
notifications.toast.enabled | boolean | In-TUI completion toasts |
notifications.sound.enabled | boolean | Chime when a background tab finishes |
notifications.sound.volume | number | Chime level, 0-1 (default 0.4) |
notifications.crossTask.enabled | boolean | Toasts for tasks you aren't looking at |
Error toasts always show, even with toasts off. See Notifications for how they're delivered.
Zen mode
Zen hides the Files pane so the active workspace gets the freed width. The
engine or shell in the workspace remains visible. Toggle with ctrl+a z.
| Key | Type | Default | What it does |
|---|---|---|---|
zen.active | boolean | false | On/off. Persisted, so switching projects keeps you in zen |
Zen always keeps the Tasks rail visible, because the rail also contains the
exit affordance. A zen.keepTasks value left in your state.json by an older
Rove is ignored; nothing reads or writes it any more.
Worktree location
By default new worktrees land under ~/.rove/worktrees/<repo-key>/<slug>.
| Key | Type | Default | What it does |
|---|---|---|---|
worktree.basePath | string | ~/.rove/worktrees | Where new worktrees go |
worktree.basePath.custom | string | unset | Remembers your last custom path in the TUI |
worktree.cloneIgnored | boolean | true | Clone ignored directories into new worktrees; see below |
worktree.basePath takes an absolute path, or one starting with the
$project_dir token, which expands to each task's project root, so one setting
that gives you a per-project layout. $project_dir/.. puts worktrees next to
each repo. The token only counts as the first segment.
Only new tasks move. Existing tasks keep the path they were created with,
including legacy global and repo-local roots. worktree.basePath is
local-only: remote (SSH) worktrees go under the remote project's own path
at <project>/.rove/worktrees, and their existing .rove/worktrees remain
discoverable. No restart needed.
Cloned ignored directories
A new local task's worktree starts with no node_modules, .venv, target or
.build, because git does not check out ignored files. On macOS Rove clones
those directories from the project's main checkout into the new worktree before
.rove/init.sh runs, using APFS copy-on-write (clonefileat(2)). The copies cost
almost no disk until a file changes, so bun test and friends work at once.
init.sh still runs afterwards and stays authoritative: a lockfile that
differs from the main checkout is its job to reconcile.
| Setting | Where | Default | What it does |
|---|---|---|---|
worktree.cloneIgnored | state.json | true | false turns cloning off for every project. No restart needed |
.rove/clone-dirs | in the repo | the four names above | Directory names to clone, one per line, # comments. A non-blank file replaces the defaults, so a comment-only file clones nothing for that repo. Wins over the defaults the way init.sh wins over the state.json override |
Names are matched anywhere in the tree, so node_modules also picks up
packages/*/node_modules. Entries containing a /, exactly . or .., or
starting with -, are ignored.
A directory is cloned only when all of these hold; otherwise it is skipped without a message, exactly as before:
- the project is local (not SSH) and the task owns a worktree (project-main and directory tasks do not);
- git reports the directory as ignored and containing no tracked file;
- it is absent from the new worktree;
- source and worktree are on the same APFS volume (
st_devmatch).
A clone that fails (no /usr/bin/perl, say) is logged to the daemon log; it
never fails task creation and leaves no partial directory. Each directory is
one clonefileat(2) call, run concurrently, asynchronously: on a 2 GB,
87,000-file node_modules plus six nested ones, task creation went from 0.8 s
to about 2.5 s. du counts clones at full size, so measure with df: that
clone cost about 35 MB of free space.
Deleting a fresh task needs no --force: the four default names, when this
worktree clones them, do not count as gitignored work. A name you add to
.rove/clone-dirs is cloned but still counts, so a data directory is never
silently deletable.
Sidebar
The current tree sidebar follows persisted project/task order and supports
manual project reordering with shift+m. The t key cycles the task sort
through four orders — the persisted one, most-recently-touched, attention
(tasks blocked on you first, then ones whose turn landed unread,
most-recently-touched inside each group), and name (A→Z by title, ignoring
case, with numbers in numeric order so task 2 comes before task 10).
Projects keep their own order in every mode. The choice is saved as
activeSortMode and read back on startup; a value this build does not
recognise reads as the persisted order. Older state files may contain
tasksPane.projectFilter; the daemon still mirrors that compatibility value
for background consumers, but the current PureTUI tree does not consume it.
Machines
machines maps a local alias to another computer running Rove. Written by
rove machine add; nothing here needs hand-editing.
| Field | What |
|---|---|
host | SSH host as typed — usually an ssh_config Host alias, not a DNS name |
user | Login user, or absent to let ssh_config decide |
port | SSH port, or absent for the ssh_config default |
auth | {"kind":"key"} (agent / default identities), {"kind":"key","keyPath":"…"}, or {"kind":"password","keychainRef":{…}} — a password is never stored here, only a pointer to it |
identity | {hostname, homeDir, daemonPid} learned from the machine's last handshake. Two aliases whose triples match are one machine |
sockets | {daemon, pty} remote socket paths the machine last reported, cached so a reconnect skips a second SSH round-trip. Absent until the first successful connect; never derived locally |
addedAt | ISO timestamp of registration |
Experimental
Off by default. These can change without notice.
| Key | What it enables |
|---|---|
experimental.remoteProjects | Lets rove add --remote register a NEW project over SSH. Gates that one command only: remote projects already registered keep working — worktree routing and engine launch never read the flag — so turning it off does not disable them |
experimental.autoStatus | Tasks move to in_progress and self-report in_review |
experimental.dispatcher | Per-repo routing of field notes between sessions |
Themes
Rove bundles four themes (claude, conductor, skylight, and tokyonight)
and ten more are one command away. skylight is built around its light half:
set themeMode to light or auto to see it.
rove theme list
rove theme add https://rove.run/themes/gruvbox.json
rove theme remove gruvboxAvailable hosted: catppuccin, dracula, everforest, gruvbox,
kanagawa, nord, opencode, osaka-jade, rose-pine, solarized.
Preview them at https://rove.run/themes.
You can also drop your own <name>.json into ~/.rove/themes/. No
recompile, loaded at boot, and a user theme wins over a bundled one with the
same name. Writing one: Themes.
Keybindings
Full vocabulary: Keybindings. The configuration surface:
- Edit
~/.rove/settings/keybindings.yamlby hand (.ymlis accepted when.yamlis absent). Rove never writes it. - Changes reload live, no restart. Problems show up as warnings in Settings → Keybindings.
- A direct override replaces that binding's whole chord list;
nullor[]unbinds it. Prefix overrides set second-stroke keys and keep the original pane scope. Platform overlays (darwin:, …) win per chord. - A
plugins:section binds chords to installed plugin panes and actions. Rove ships no default plugin chords. - Unknown ids are ignored with a warning; a typo never breaks the keymap.
Notifications and sound
Three kinds: done (green), needs_input (yellow), error (red). Yellow and
red outrank green when both fire for the same tab. Three delivery channels:
-
Toasts. In-TUI, 4.5 seconds. Error toasts always show, even with toasts disabled: a failure shouldn't vanish because you turned off completion popups.
-
Desktop notification. Rove emits an OSC 9 escape that iTerm2, kitty, WezTerm, and Ghostty turn into a real OS notification; other terminals ignore it. Because it travels down the terminal stream, it reaches you over SSH. No separate switch — it rides the same
notifications.sound.enabledtoggle as the chime. -
Sound. A short chime when a background tab finishes. Rove uses the first player it finds on
PATH(ffplay,mpv,mpg123, …afplay,play,aplay, …), and on Windows falls back to PowerShell. With none installed it's silent and the terminal bell is the fallback.Volume is a property of the audio Rove hands the player, not a flag it passes: several players (
afplay,aplay, and the Windows fallback, whoseMedia.SoundPlayerhas no volume API) accept no volume argument at all, so Rove scales the chime's samples itself and caches one copy per level. Cycle it from Settings → General → Chime volume, or setnotifications.sound.volumedirectly;0is silent.
Custom engines
Built-in engines are claude, codex, copilot, kimi, pi, and omp.
You can register any other CLI from Settings → Engines, or by hand:
{
"customEngineIds": ["aider"],
"engineCommand.aider": "aider --model sonnet",
"engineName.aider": "Aider"
}Switching an engine OFF in Settings → Engines (space) records it under
disabledEngineIds; it keeps every override and simply stops being offered
when you pick an engine for a task. That covers the headless path too: a
disabled engine is skipped by rove api add's repo default, so switching one
off after using it in a project does not leave that project still launching it.
The global default engine can't be left disabled; switching it off hands the
default to the first engine still on.
Being in customEngineIds is the registration. There's no other step.
Settings → Engines rejects a blank id, one that shadows a built-in, and one
already registered; it lowercases and trims what you type and accepts the rest.
Keep ids to lowercase letters, digits, - and _: the id becomes both a
--command <id> argument and a key in state.json, so a space or a quote in
one makes it awkward to pass and awkward to hand-edit.
A custom engine launches and runs like any other, but Rove deliberately
doesn't guess at its internals — no history reader, no account detection, no
activity hooks, no session resume — unless you declare
"engineProtocol.<id>" (one of the built-in ids: claude, codex,
copilot, kimi, pi, omp), which borrows that built-in's adapter for
transcript reads and delivery. More in Engines.
Settings → Engines asks for it while adding the engine — a list of the
built-ins plus None, so the generic adapter is something you choose rather
than something a typo leaves you with — and prints the answer under the engine's
row afterwards. Changing it means removing the engine (x) and adding it again,
or editing the key here by hand.
Claude Code plugin
Rove integrates with Claude Code through global activity hooks (so the sidebar can show working / done / needs-input for every session) and the companion agent skill. There are two ways to get both, and you should run exactly one:
-
Default (no action needed): every Rove launch idempotently writes its hooks into
~/.claude/settings.json, androve skill installplaces the skill. IfCLAUDE_CONFIG_DIRis set to a nonblank path, hooks instead go into<CLAUDE_CONFIG_DIR>/settings.json. This is what most existing installs use. -
The Claude Code plugin. One install carries hooks and skill together, with no PATH or settings.json involvement:
/plugin marketplace add Sma1lboy/rove /plugin install rove@roveThe plugin's hook commands call a bundled wrapper by absolute path, so they work even when
roveisn't on the shell's PATH. The bundled skill is versioned with the plugin rather than with Rove itself (/plugin updaterefreshes both), so Rove's skill staleness prompts step aside while the plugin is enabled.
Once Rove sees the plugin enabled, it stops writing the Claude hooks into
settings.json on launch. If you were running Rove before installing the
plugin, the old settings-managed hooks are still there and every event would
fire twice. Rove warns about this at startup and the fix is one command:
rove hook cleanupThat removes Rove's entries from the active profile's settings.json; your
other hooks, including commands in the same group, are preserved. Installation
and cleanup leave invalid or unreadable settings unchanged. They also refuse
non-regular files and files over 8 MiB. JSON rewrites use owner-only read/write
permissions (0600). Startup cleanup of retired global hooks uses this same
profile; explicitly saved repository or settings-file cleanup paths still apply.
If you also have a pre-plugin skill copy under
~/.claude/skills/rove (or …/rove), delete that directory. The plugin's
bundled copy replaces it. Rove never edits or removes either one silently.
Uninstalling or disabling the plugin reverses the handoff: the next Rove launch reinstalls the settings-managed hooks automatically.
Only Claude Code is affected. Codex (and other engines') hook integration is engine-owned and unchanged by the plugin.
Per-repo init
A repo can ship two files in its own .rove/ directory:
.rove/init.sh. Runs in each new task worktree before the engine starts, once per worktree. Use it forbun install, direnv, codegen..rove/init-prompt.md. Sent as the engine's first message.
init.sh comes from the repo, and Rove runs it without asking — in the same
shell that then execs the engine, with your account's privileges. That is the
same level of trust you extend by running the repo's own build or test
command, so it is worth a look at .rove/init.sh before you create the first
task in a repository you did not write.
Files under .rove/ win over per-user overrides set with rove repo set.
For init.sh, the file only has to exist, even if empty. Prompt files must
be non-empty. The retired repository directory is no longer read.
The PR action also reads .rove/pr-instructions.md as its prompt template;
{{branch}}, {{targetBranch}}, {{dirtyCountSentence}}, and
{{upstreamSentence}} are substituted (unknown {{…}} passes through).
The file must be non-empty.