- R-CLI
- Permissions
R-CLI
Permissions
Control which tools R-CLI runs automatically, which ask first, and which are blocked outright.
Permissions decide what R-CLI may do without asking you. Read-only work runs freely; anything that changes your machine asks first, unless you have said otherwise.
By default R-CLI starts in Manual mode: it reads files and runs safe commands on its own, and asks before every edit, write, or non-trivial shell command.
Permission modes
Press Shift+Tab to cycle modes during a session. The current mode is shown in the status bar.
| Mode | Indicator | Behavior |
|---|---|---|
manual | ⏸ Manual | Default. Asks before anything that is not read-only or explicitly allowed. |
acceptEdits | ◆ Accept Edits | File edits and writes inside your project folder run without asking. Shell commands and everything else still ask. |
auto | ✦ Auto | Reserved for automatic classification. Today it behaves like manual. |
bypass | » Bypass | Runs everything except deny and ask rules. Not in the Shift+Tab cycle. |
Set the mode at startup instead:
backboard --permission-mode acceptEdits
bypass mode lets the agent run any command and edit any file without asking, including outside your project folder. Use it only in throwaway environments such as containers or CI. It is deliberately unreachable from Shift+Tab — you must pass the flag or set it in config.
An unrecognized value does not stop the run. R-CLI falls back to manual and prints a warning:
Unknown --permission-mode "yolo"; using manual mode. Valid: manual, acceptEdits, auto, bypass.
Configuration file
Permission settings live in your project, at the repository root:
<repo-root>/.backboard/settings.json
The repo root is found by walking up from your working directory looking for .git, so every subdirectory of a project shares one policy.
{
"permissions": {
"mode": "manual",
"allow": [
"execute(git status:*)",
"execute(bun test:*)",
"read(src/**)"
],
"deny": [
"execute(rm:*)",
"write(.env)"
],
"ask": [
"execute(git push:*)"
]
}
}
| Key | Type | Description |
|---|---|---|
mode | string | Starting permission mode. Overridden by --permission-mode. |
allow | string[] | Rules that run without asking. |
deny | string[] | Rules that are always blocked. |
ask | string[] | Rules that always prompt, even in bypass mode. |
Mode precedence is --permission-mode flag, then permissions.mode, then manual.
A missing or malformed settings.json is treated as empty settings — R-CLI falls back to defaults rather than failing to start.
Rule syntax
A rule is a tool name, optionally followed by a pattern in parentheses:
execute # the whole Execute tool
execute(git status:*) # commands starting with "git status"
write(.env) # writing exactly .env
read(src/**) # reading anything under src/
Tool names are lower-cased, for example execute, read, write, edit, find_skill, find_mcp.
Patterns are matched against the meaningful part of the call — the command string for execute, the file path for edit and write:
| Pattern form | Matches |
|---|---|
prefix:* | Content equal to prefix, or starting with prefix followed by a space |
Contains * | Glob match, for example read(src/**) |
| Anything else | Exact string equality |
prefix:* matches on whole space-delimited tokens, not raw text. execute(git:*) allows every git subcommand, including git push and git reset --hard. Scope allow rules as tightly as you can.
How a decision is made
For every tool call, the first matching step wins:
Deny rule
A matching deny rule blocks the call. Nothing overrides this.
Ask rule
A matching ask rule always prompts, even in bypass mode.
Tool verdict
Some tools decide for themselves. In acceptEdits mode, Edit, Write, and ApplyPatch approve paths inside the directory R-CLI was started in — not the whole repository. A symlink that resolves outside that directory is still rejected.
Read-only check
Tools that only read are allowed automatically, in every mode.
Bypass mode
In bypass mode, everything remaining is allowed.
Allow rule
A matching allow rule permits the call.
Otherwise, ask
Anything unmatched prompts you.
deny and ask are evaluated before the bypass gate on purpose: no mode can skip them. This makes deny a reliable guardrail even in automated runs.
Safe commands
Some read-only shell commands are approved automatically without a rule:
git status git diff git log git show
ls pwd cat head tail wc which echo
grep rg find
bun test bun run typecheck
Read-only forms of git branch are also approved — --list, --show-current, -a, -r, -v, and their combinations.
These are treated as unsafe and will still prompt:
- Anything containing
$(, a backtick,>, or<. - Chained commands where any segment is not itself safe — segments are split on
&&,||,;,|,&, and newlines. findwith-exec,-execdir,-ok,-okdir,-delete,-fprint,-fprintf,-fprint0, or-fls.rgwith--pre,--pre-glob,--hostname-bin,--search-zip, or-z.git status,git diff,git log, orgit showwith--output.git branchwith anything other than read-only flags such as--listor--show-current. A bare branch name mutates, so it prompts.- Background commands, which are never auto-approved by the safe list.
Quotes are stripped before the find, rg, and git flag checks, so '-delete' cannot dodge the guard. The $(, backtick, >, and < check runs on the raw command.
The permission prompt
When a call needs approval, R-CLI shows the tool, a one-line summary of what it will do, and three choices:
| Choice | Effect |
|---|---|
Yes, allow once | Runs this one call. |
Yes, always allow (<rule>) | Runs it and saves the shown rule to settings.json. |
No, deny | Blocks the call and tells the agent. |
The “always allow” option always shows the exact rule it will save, so you can see how broad it is before accepting.
R-CLI scopes that rule conservatively:
| Command shape | Saved rule |
|---|---|
| A single path-like argument | Exact path, for example write(src/index.ts) |
| A sensitive command | The exact full command, never a prefix |
| Anything else | First two tokens, for example execute(bun test:*) |
Sensitive commands that never generalize include rm, rmdir, mv, dd, mkfs, ln, chmod, chown, chgrp, curl, wget, kill, killall, pkill, sudo, su, git, docker, ssh, scp, and rsync. Approving rm -rf build once will not allow rm in general.
This list is best-effort rather than exhaustive. Prefer an explicit deny rule for anything you never want run.
Headless and automation
Non-interactive runs cannot show a prompt, so anything that would ask is denied:
Permission required for Execute — permission prompts are unavailable in this context.
This applies to --print one-shot mode. To script R-CLI, either pre-approve exactly what it needs:
{
"permissions": {
"allow": ["execute(bun test:*)", "execute(git diff:*)"],
"deny": ["execute(git push:*)"]
}
}
Or, in a disposable environment only:
backboard --print "run the test suite" --permission-mode bypass
Pairing bypass with a deny list is safer than bypass alone. Deny rules still apply, so you can block destructive commands while letting everything else run unattended.
Sub-agents
Sub-agents inherit the parent session’s mode and rules, but can never prompt. Each of their tool calls is checked individually, and anything that would ask is denied.
Sub-agents also cannot install third-party code. They can call the skill and MCP discovery tools, but those tools refuse to install anything from a sub-agent and report the match back instead. Only the main agent can request an install, and only with your confirmation.
Related
- Command reference — the
--permission-modeflag and config file locations - Checkpoints — undo file changes the agent already made
- Skills and discovery — confirmation before installing third-party skills and MCP servers