Add Auto permission mode
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (push) Successful in 5s
Build App (Preview) / test (pull_request) Successful in 4m38s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m47s
Build Container / build-container (pull_request) Successful in 10m19s
Build App (Preview) / build-windows (pull_request) Successful in 5m57s
Build App (Preview) / build-linux (pull_request) Canceled after 14s
Build App (Preview) / prune-previews (pull_request) Canceled after 0s
Build App (Preview) / compute-version (pull_request) Successful in 5s
Secret Scan / scan (push) Successful in 5s
Build App (Preview) / test (pull_request) Successful in 4m38s
Secret Scan / scan (pull_request) Successful in 4s
Build App (Preview) / create-release (pull_request) Successful in 1s
Build App (Preview) / build-macos (pull_request) Successful in 2m47s
Build Container / build-container (pull_request) Successful in 10m19s
Build App (Preview) / build-windows (pull_request) Successful in 5m57s
Build App (Preview) / build-linux (pull_request) Canceled after 14s
Build App (Preview) / prune-previews (pull_request) Canceled after 0s
Claude Code now ships `--permission-mode auto`, where a safety classifier approves routine actions and blocks risky ones without prompting. Expose it as a fifth mode between Accept Edits and Bypass: terminals, resumed sessions, the tab badge, and the scheduler's task runner all map it to the flag. Headless scheduled runs don't stall in Auto (blocked actions are denied, not prompted), so the task editor shows a softer note instead of the stall warning. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -469,6 +469,20 @@ mod tests {
|
||||
assert!(!cmd[2].contains(" -n "), "empty name must add no flag: {}", cmd[2]);
|
||||
}
|
||||
|
||||
/// Auto mode is passed as a `--permission-mode` value, not its own flag.
|
||||
#[test]
|
||||
fn build_terminal_cmd_passes_auto_permission_mode() {
|
||||
let mut p = project("anthropic", serde_json::Value::Null);
|
||||
p.permission_mode = Some(crate::models::project::PermissionMode::Auto);
|
||||
let cmd = build_claude_terminal_cmd(&p, None, None);
|
||||
|
||||
assert!(
|
||||
cmd[2].contains("exec claude '--permission-mode' 'auto'"),
|
||||
"got: {}",
|
||||
cmd[2]
|
||||
);
|
||||
}
|
||||
|
||||
/// The Bedrock-profile path keeps its AWS validation *and* gains the
|
||||
/// prelude, immediately before the exec.
|
||||
#[test]
|
||||
|
||||
@@ -166,6 +166,9 @@ pub enum PermissionMode {
|
||||
Default,
|
||||
/// Auto-accept file edits, prompt for everything else.
|
||||
AcceptEdits,
|
||||
/// Claude Code's classifier approves safe actions and blocks risky ones,
|
||||
/// without prompting.
|
||||
Auto,
|
||||
/// Skip all permission prompts.
|
||||
Bypass,
|
||||
}
|
||||
@@ -180,6 +183,7 @@ impl PermissionMode {
|
||||
PermissionMode::AcceptEdits => {
|
||||
vec!["--permission-mode".to_string(), "acceptEdits".to_string()]
|
||||
}
|
||||
PermissionMode::Auto => vec!["--permission-mode".to_string(), "auto".to_string()],
|
||||
PermissionMode::Bypass => vec!["--dangerously-skip-permissions".to_string()],
|
||||
}
|
||||
}
|
||||
@@ -191,6 +195,7 @@ impl PermissionMode {
|
||||
PermissionMode::Plan => "plan",
|
||||
PermissionMode::Default => "default",
|
||||
PermissionMode::AcceptEdits => "acceptEdits",
|
||||
PermissionMode::Auto => "auto",
|
||||
PermissionMode::Bypass => "bypass",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const MODE_BADGE: Record<PermissionMode, { text: string; className: string }> =
|
||||
plan: { text: "plan", className: "bg-[var(--bg-tertiary)] text-[var(--text-secondary)]" },
|
||||
default: { text: "ask", className: "bg-[var(--bg-tertiary)] text-[var(--text-secondary)]" },
|
||||
acceptEdits: { text: "edits", className: "bg-[var(--accent-muted)] text-[var(--accent)]" },
|
||||
auto: { text: "auto", className: "bg-[var(--accent-muted)] text-[var(--accent)]" },
|
||||
bypass: { text: "bypass", className: "bg-[var(--warning-muted)] text-[var(--warning)]" },
|
||||
};
|
||||
|
||||
|
||||
@@ -75,11 +75,11 @@ describe("PermissionModeControl", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders all four modes as a radio group with the effective one checked", () => {
|
||||
it("renders all five modes as a radio group with the effective one checked", () => {
|
||||
render(<PermissionModeControl project={baseProject} onChange={onChange} />);
|
||||
const group = screen.getByRole("radiogroup", { name: "Permission mode" });
|
||||
expect(group).toBeInTheDocument();
|
||||
expect(screen.getAllByRole("radio")).toHaveLength(4);
|
||||
expect(screen.getAllByRole("radio")).toHaveLength(5);
|
||||
expect(screen.getByRole("radio", { name: "Default" })).toHaveAttribute(
|
||||
"aria-checked",
|
||||
"true",
|
||||
@@ -92,6 +92,14 @@ describe("PermissionModeControl", () => {
|
||||
expect(onChange).toHaveBeenCalledWith("acceptEdits");
|
||||
});
|
||||
|
||||
it("offers Auto between Accept Edits and Bypass", () => {
|
||||
render(<PermissionModeControl project={baseProject} onChange={onChange} />);
|
||||
const labels = screen.getAllByRole("radio").map((r) => r.textContent);
|
||||
expect(labels).toEqual(["Plan", "Default", "Accept Edits", "Auto", "Bypass"]);
|
||||
fireEvent.click(screen.getByRole("radio", { name: "Auto" }));
|
||||
expect(onChange).toHaveBeenCalledWith("auto");
|
||||
});
|
||||
|
||||
it("moves selection with the arrow keys", () => {
|
||||
render(<PermissionModeControl project={baseProject} onChange={onChange} />);
|
||||
fireEvent.keyDown(screen.getByRole("radiogroup", { name: "Permission mode" }), {
|
||||
|
||||
@@ -9,6 +9,11 @@ export const PERMISSION_MODES: Segment<PermissionMode>[] = [
|
||||
label: "Accept Edits",
|
||||
hint: "File edits are auto-approved; other tools still prompt.",
|
||||
},
|
||||
{
|
||||
value: "auto",
|
||||
label: "Auto",
|
||||
hint: "A safety classifier approves routine actions and blocks risky ones, without prompting.",
|
||||
},
|
||||
{
|
||||
value: "bypass",
|
||||
label: "Bypass",
|
||||
|
||||
@@ -159,12 +159,18 @@ describe("TaskEditorModal", () => {
|
||||
});
|
||||
|
||||
it("warns that a headless run cannot answer a permission prompt", async () => {
|
||||
// Bypass is the only mode where an unattended run is safe from stalling.
|
||||
// Bypass (and Auto, below) are the modes where an unattended run cannot stall.
|
||||
await renderEditor(null, { ...baseProject, permission_mode: "bypass" });
|
||||
expect(screen.getByText(/headless/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/cannot answer a permission prompt/i)).toBeNull();
|
||||
});
|
||||
|
||||
it("tells Auto mode that blocked actions are denied, not prompted", async () => {
|
||||
await renderEditor(null, { ...baseProject, permission_mode: "auto" });
|
||||
expect(screen.queryByText(/cannot answer a permission prompt/i)).toBeNull();
|
||||
expect(screen.getByText(/blocks are denied/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("spells out the stall risk in any non-Bypass mode", async () => {
|
||||
await renderEditor(null, { ...baseProject, permission_mode: "default" });
|
||||
expect(screen.getByText(/cannot answer a permission prompt/i)).toBeInTheDocument();
|
||||
|
||||
@@ -301,11 +301,17 @@ export default function TaskEditorModal({ project, task, onClose, onSaved }: Pro
|
||||
terminal attached, using this project’s permission mode (
|
||||
<strong className="text-[var(--text-primary)]">{modeLabel}</strong>).
|
||||
</p>
|
||||
{mode !== "bypass" && (
|
||||
{mode === "auto" && (
|
||||
<p className="text-xs text-[var(--text-secondary)]">
|
||||
In Auto mode nothing prompts: actions the safety classifier blocks are denied and the
|
||||
run carries on without them, so check the log if a task seems to have skipped a step.
|
||||
</p>
|
||||
)}
|
||||
{mode !== "bypass" && mode !== "auto" && (
|
||||
<p className="text-xs text-[var(--warning)]">
|
||||
A headless run cannot answer a permission prompt. In {modeLabel} mode the task may
|
||||
stall and produce an empty log; set the mode to Bypass in the Config tab for
|
||||
unattended runs.
|
||||
stall and produce an empty log; set the mode to Auto or Bypass in the Config tab
|
||||
for unattended runs.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -126,7 +126,7 @@ export const CUSTOM_ENDPOINT_BACKENDS: readonly Backend[] = [
|
||||
];
|
||||
|
||||
/** Mirrors Rust `PermissionMode` (serde camelCase). */
|
||||
export type PermissionMode = "plan" | "default" | "acceptEdits" | "bypass";
|
||||
export type PermissionMode = "plan" | "default" | "acceptEdits" | "auto" | "bypass";
|
||||
|
||||
export type BedrockAuthMethod = "static_credentials" | "profile" | "bearer_token";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user