Make CLI auto-approve by default with require-approval opt-in (#11424)

Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
Chris Estreich 2026-02-11 16:12:05 -08:00 committed by GitHub
parent bd4cd07e88
commit 6c8b9dfa26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 58 additions and 50 deletions

View file

@ -67,7 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Skip onboarding flow when a provider is explicitly specified via `--provider` flag or saved in settings
- Unified permission flags: Combined `-y`, `--yes`, and `--dangerously-skip-permissions` into a single option for Claude Code-like CLI compatibility
- Unified permission flags: Combined approval-skipping flags into a single option for Claude Code-like CLI compatibility
- Improved Roo Code Router authentication flow and error messaging
### Fixed

View file

@ -66,7 +66,7 @@ pnpm --filter @roo-code/cli build
### Interactive Mode (Default)
By default, the CLI prompts for approval before executing actions:
By default, the CLI auto-approves actions and runs in interactive TUI mode:
```bash
export OPENROUTER_API_KEY=sk-or-v1-...
@ -82,24 +82,23 @@ roo -w ~/Documents/my-project
In interactive mode:
- Tool executions prompt for yes/no approval
- Commands prompt for yes/no approval
- Followup questions show suggestions and wait for user input
- Browser and MCP actions prompt for approval
- Tool executions are auto-approved
- Commands are auto-approved
- Followup questions show suggestions with a 60-second timeout, then auto-select the first suggestion
- Browser and MCP actions are auto-approved
### Non-Interactive Mode (`-y`)
### Approval-Required Mode (`--require-approval`)
For automation and scripts, use `-y` to auto-approve all actions:
If you want manual approval prompts, enable approval-required mode:
```bash
roo "Refactor the utils.ts file" -y -w ~/Documents/my-project
roo "Refactor the utils.ts file" --require-approval -w ~/Documents/my-project
```
In non-interactive mode:
In approval-required mode:
- Tool, command, browser, and MCP actions are auto-approved
- Followup questions show a 60-second timeout, then auto-select the first suggestion
- Typing any key cancels the timeout and allows manual input
- Tool, command, browser, and MCP actions prompt for yes/no approval
- Followup questions wait for manual input (no auto-timeout)
### Roo Code Cloud Authentication
@ -147,23 +146,23 @@ Tokens are valid for 90 days. The CLI will prompt you to re-authenticate when yo
## Options
| Option | Description | Default |
| ------------------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------- |
| `[prompt]` | Your prompt (positional argument, optional) | None |
| `--prompt-file <path>` | Read prompt from a file instead of command line argument | None |
| `-w, --workspace <path>` | Workspace path to operate in | Current directory |
| `-p, --print` | Print response and exit (non-interactive mode) | `false` |
| `-e, --extension <path>` | Path to the extension bundle directory | Auto-detected |
| `-d, --debug` | Enable debug output (includes detailed debug information, prompts, paths, etc) | `false` |
| `-y, --yes, --dangerously-skip-permissions` | Auto-approve all actions (use with caution) | `false` |
| `-k, --api-key <key>` | API key for the LLM provider | From env var |
| `--provider <provider>` | API provider (roo, anthropic, openai, openrouter, etc.) | `openrouter` (or `roo` if authenticated) |
| `-m, --model <model>` | Model to use | `anthropic/claude-opus-4.6` |
| `--mode <mode>` | Mode to start in (code, architect, ask, debug, etc.) | `code` |
| `-r, --reasoning-effort <effort>` | Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh) | `medium` |
| `--ephemeral` | Run without persisting state (uses temporary storage) | `false` |
| `--oneshot` | Exit upon task completion | `false` |
| `--output-format <format>` | Output format with `--print`: `text`, `json`, or `stream-json` | `text` |
| Option | Description | Default |
| --------------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------- |
| `[prompt]` | Your prompt (positional argument, optional) | None |
| `--prompt-file <path>` | Read prompt from a file instead of command line argument | None |
| `-w, --workspace <path>` | Workspace path to operate in | Current directory |
| `-p, --print` | Print response and exit (non-interactive mode) | `false` |
| `-e, --extension <path>` | Path to the extension bundle directory | Auto-detected |
| `-d, --debug` | Enable debug output (includes detailed debug information, prompts, paths, etc) | `false` |
| `-a, --require-approval` | Require manual approval before actions execute | `false` |
| `-k, --api-key <key>` | API key for the LLM provider | From env var |
| `--provider <provider>` | API provider (roo, anthropic, openai, openrouter, etc.) | `openrouter` (or `roo` if authenticated) |
| `-m, --model <model>` | Model to use | `anthropic/claude-opus-4.6` |
| `--mode <mode>` | Mode to start in (code, architect, ask, debug, etc.) | `code` |
| `-r, --reasoning-effort <effort>` | Reasoning effort level (unspecified, disabled, none, minimal, low, medium, high, xhigh) | `medium` |
| `--ephemeral` | Run without persisting state (uses temporary storage) | `false` |
| `--oneshot` | Exit upon task completion | `false` |
| `--output-format <format>` | Output format with `--print`: `text`, `json`, or `stream-json` | `text` |
## Auth Commands

View file

@ -242,7 +242,8 @@ Routes asks to appropriate handlers:
- Uses type guards: `isIdleAsk()`, `isInteractiveAsk()`, etc.
- Coordinates between `OutputManager` and `PromptManager`
- In non-interactive mode (`-y` flag), auto-approves everything
- By default, the CLI auto-approves tool/command/browser/MCP actions
- In `--require-approval` mode, those actions prompt for manual approval
### OutputManager
@ -320,7 +321,7 @@ if (isInteractiveAsk(ask)) {
Enable with `-d` flag. Logs go to `~/.roo/cli-debug.log`:
```bash
roo -d -y -P "Build something" --no-tui
roo -d -P "Build something" --no-tui
```
View logs:

View file

@ -15,7 +15,7 @@
"test": "vitest run",
"build": "tsup",
"build:extension": "pnpm --filter roo-cline bundle",
"dev": "ROO_AUTH_BASE_URL=https://app.roocode.com ROO_SDK_BASE_URL=https://cloud-api.roocode.com ROO_CODE_PROVIDER_URL=https://api.roocode.com/proxy tsx src/index.ts -y",
"dev": "ROO_AUTH_BASE_URL=https://app.roocode.com ROO_SDK_BASE_URL=https://cloud-api.roocode.com ROO_CODE_PROVIDER_URL=https://api.roocode.com/proxy tsx src/index.ts",
"dev:local": "ROO_AUTH_BASE_URL=http://localhost:3000 ROO_SDK_BASE_URL=http://localhost:3001 ROO_CODE_PROVIDER_URL=http://localhost:8080/proxy tsx src/index.ts",
"clean": "rimraf dist .turbo"
},

View file

@ -65,8 +65,10 @@ export async function run(promptArg: string | undefined, flagOptions: FlagOption
flagOptions.reasoningEffort || settings.reasoningEffort || DEFAULT_FLAGS.reasoningEffort
const effectiveProvider = flagOptions.provider ?? settings.provider ?? (rooToken ? "roo" : "openrouter")
const effectiveWorkspacePath = flagOptions.workspace ? path.resolve(flagOptions.workspace) : process.cwd()
const effectiveDangerouslySkipPermissions =
flagOptions.yes || flagOptions.dangerouslySkipPermissions || settings.dangerouslySkipPermissions || false
const legacyRequireApprovalFromSettings =
settings.requireApproval ??
(settings.dangerouslySkipPermissions === undefined ? undefined : !settings.dangerouslySkipPermissions)
const effectiveRequireApproval = flagOptions.requireApproval || legacyRequireApprovalFromSettings || false
const effectiveExitOnComplete = flagOptions.print || flagOptions.oneshot || settings.oneshot || false
const extensionHostOptions: ExtensionHostOptions = {
@ -77,7 +79,7 @@ export async function run(promptArg: string | undefined, flagOptions: FlagOption
model: effectiveModel,
workspacePath: effectiveWorkspacePath,
extensionPath: path.resolve(flagOptions.extension || getDefaultExtensionPath(__dirname)),
nonInteractive: effectiveDangerouslySkipPermissions,
nonInteractive: !effectiveRequireApproval,
exitOnError: flagOptions.exitOnError,
ephemeral: flagOptions.ephemeral,
debug: flagOptions.debug,

View file

@ -18,8 +18,7 @@ program
.option("-p, --print", "Print response and exit (non-interactive mode)", false)
.option("-e, --extension <path>", "Path to the extension bundle directory")
.option("-d, --debug", "Enable debug output (includes detailed debug information)", false)
.option("-y, --yes", "Auto-approve all prompts (use with caution)", false)
.option("--dangerously-skip-permissions", "Alias for --yes", false)
.option("-a, --require-approval", "Require manual approval for actions", false)
.option("-k, --api-key <key>", "API key for the LLM provider")
.option("--provider <provider>", "API provider (roo, anthropic, openai, openrouter, etc.)")
.option("-m, --model <model>", "Model to use", DEFAULT_FLAGS.model)

View file

@ -179,20 +179,20 @@ describe("Settings Storage", () => {
expect(loaded.reasoningEffort).toBe("low")
})
it("should support dangerouslySkipPermissions setting", async () => {
await saveSettings({ dangerouslySkipPermissions: true })
it("should support requireApproval setting", async () => {
await saveSettings({ requireApproval: true })
const loaded = await loadSettings()
expect(loaded.dangerouslySkipPermissions).toBe(true)
expect(loaded.requireApproval).toBe(true)
})
it("should support all settings together including dangerouslySkipPermissions", async () => {
it("should support all settings together including requireApproval", async () => {
const allSettings = {
mode: "architect",
provider: "anthropic" as const,
model: "claude-sonnet-4-20250514",
reasoningEffort: "high" as const,
dangerouslySkipPermissions: true,
requireApproval: true,
}
await saveSettings(allSettings)
@ -202,7 +202,7 @@ describe("Settings Storage", () => {
expect(loaded.provider).toBe("anthropic")
expect(loaded.model).toBe("claude-sonnet-4-20250514")
expect(loaded.reasoningEffort).toBe("high")
expect(loaded.dangerouslySkipPermissions).toBe(true)
expect(loaded.requireApproval).toBe(true)
})
it("should support oneshot setting", async () => {
@ -218,7 +218,7 @@ describe("Settings Storage", () => {
provider: "anthropic" as const,
model: "claude-sonnet-4-20250514",
reasoningEffort: "high" as const,
dangerouslySkipPermissions: true,
requireApproval: true,
oneshot: true,
}
@ -229,8 +229,15 @@ describe("Settings Storage", () => {
expect(loaded.provider).toBe("anthropic")
expect(loaded.model).toBe("claude-sonnet-4-20250514")
expect(loaded.reasoningEffort).toBe("high")
expect(loaded.dangerouslySkipPermissions).toBe(true)
expect(loaded.requireApproval).toBe(true)
expect(loaded.oneshot).toBe(true)
})
it("should still load legacy dangerouslySkipPermissions setting", async () => {
await saveSettings({ dangerouslySkipPermissions: true })
const loaded = await loadSettings()
expect(loaded.dangerouslySkipPermissions).toBe(true)
})
})
})

View file

@ -24,8 +24,7 @@ export type FlagOptions = {
print: boolean
extension?: string
debug: boolean
yes: boolean
dangerouslySkipPermissions: boolean
requireApproval: boolean
exitOnError: boolean
apiKey?: string
provider?: SupportedProvider
@ -58,7 +57,9 @@ export interface CliSettings {
model?: string
/** Default reasoning effort level */
reasoningEffort?: ReasoningEffortFlagOptions
/** Auto-approve all prompts (use with caution) */
/** Require manual approval for tools/commands/browser/MCP actions */
requireApproval?: boolean
/** @deprecated Legacy inverse setting kept for backward compatibility */
dangerouslySkipPermissions?: boolean
/** Exit upon task completion */
oneshot?: boolean

View file

@ -43,7 +43,6 @@ export const runTaskWithCli = async ({ run, task, publish, logger, jobToken }: R
promptSourcePath,
"--workspace",
workspacePath,
"--yes",
"--reasoning-effort",
"disabled",
"--oneshot",