Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/claude-code-permission-mode-auto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@aoagents/ao-plugin-agent-claude-code": minor
"@aoagents/ao-core": minor
"@aoagents/ao-cli": minor
---

feat(agent-claude-code): support Claude Code's classifier-based `auto` permission mode

Adds a new `auto` value to `AgentPermissionMode`. When set, the claude-code plugin emits `--permission-mode auto` instead of `--dangerously-skip-permissions`, letting Claude Code's built-in classifier decide per tool whether to prompt. Existing `permissionless` and `auto-edit` behavior is unchanged. Other agent plugins fall through to their default behavior for `auto` (documented in the type's JSDoc).
2 changes: 1 addition & 1 deletion packages/cli/src/lib/config-instruction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ projects:
# ── Agent configuration (optional) ────────────────────────────
agentConfig:
permissions: permissionless # permissionless | default | auto-edit | suggest
permissions: permissionless # permissionless | default | auto-edit | auto | suggest
model: claude-sonnet-4-20250514
# ── Agent rules (optional) ────────────────────────────────────
Expand Down
7 changes: 5 additions & 2 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ const NotifierConfigSchema = z
.superRefine((value, ctx) => validatePluginConfigFields(value, ctx, "Notifier"));

const AgentPermissionSchema = z
.enum(["permissionless", "default", "auto-edit", "suggest", "skip"])
.enum(["permissionless", "default", "auto-edit", "auto", "suggest", "skip"])
.default("permissionless")
.transform((value) => (value === "skip" ? "permissionless" : value));

Expand All @@ -215,7 +215,10 @@ const AgentSpecificConfigSchema = z
const RoleAgentSpecificConfigSchema = z
.object({
permissions: z
.union([z.enum(["permissionless", "default", "auto-edit", "suggest"]), z.literal("skip")])
.union([
z.enum(["permissionless", "default", "auto-edit", "auto", "suggest"]),
z.literal("skip"),
])
.optional(),
model: z.string().optional(),
orchestratorModel: z.string().optional(),
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/global-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export const LocalProjectConfigSchema = z
agentConfig: z
.object({
permissions: z
.enum(["permissionless", "default", "auto-edit", "suggest", "skip"])
.enum(["permissionless", "default", "auto-edit", "auto", "suggest", "skip"])
.optional(),
model: z.string().optional(),
orchestratorModel: z.string().optional(),
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1627,12 +1627,13 @@ export interface OpenCodeAgentConfig extends AgentSpecificConfig {
* - permissionless: run without interactive permission prompts (most permissive mode).
* - default: use the agent's normal/default permission model.
* - auto-edit: automatically approve edit actions where the agent supports granular approval policies.
* - auto: agent-driven auto mode where supported (e.g. Claude Code's --permission-mode auto).
* - suggest: conservative mode that asks for approval on higher-risk/untrusted actions where supported.
*
* Note: Not every agent exposes all granular policies; plugins map these modes to
* their closest supported behavior.
*/
export type AgentPermissionMode = "permissionless" | "default" | "auto-edit" | "suggest";
export type AgentPermissionMode = "permissionless" | "default" | "auto-edit" | "auto" | "suggest";

/** Backward-compatible legacy alias accepted in config parsing. */
export type LegacyAgentPermissionMode = "skip";
Expand All @@ -1649,6 +1650,7 @@ export function normalizeAgentPermissionMode(
mode !== "permissionless" &&
mode !== "default" &&
mode !== "auto-edit" &&
mode !== "auto" &&
mode !== "suggest"
) {
if (mode === "skip") return "permissionless";
Expand Down
25 changes: 25 additions & 0 deletions packages/plugins/agent-claude-code/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,12 @@ describe("getLaunchCommand", () => {
expect(cmd).toContain("--dangerously-skip-permissions");
});

it("maps permissions=auto to --permission-mode auto on Claude", () => {
const cmd = agent.getLaunchCommand(makeLaunchConfig({ permissions: "auto" }));
expect(cmd).toContain("--permission-mode auto");
expect(cmd).not.toContain("--dangerously-skip-permissions");
});

it("shell-escapes model argument", () => {
const cmd = agent.getLaunchCommand(makeLaunchConfig({ model: "claude-opus-4-6" }));
expect(cmd).toContain("--model 'claude-opus-4-6'");
Expand Down Expand Up @@ -684,6 +690,25 @@ describe("getSessionInfo", () => {
expect(command).toBe("claude --resume 'persisted-uuid'");
expect(mockReaddir).not.toHaveBeenCalled();
});

it("propagates --permission-mode auto on restore when project is configured for auto", async () => {
const agent = create();
const session = makeSession({
workspacePath: "/workspace/test-project",
metadata: { claudeSessionUuid: "persisted-uuid" },
});

const command = await agent.getRestoreCommand!(session, {
name: "test-project",
repo: "owner/repo",
path: "/workspace/test-project",
defaultBranch: "main",
sessionPrefix: "test",
agentConfig: { permissions: "auto" },
});

expect(command).toBe("claude --resume 'persisted-uuid' --permission-mode auto");
});
});

describe("cost estimation", () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/plugins/agent-claude-code/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,8 @@ function createClaudeCodeAgent(): Agent {
const permissionMode = normalizeAgentPermissionMode(config.permissions);
if (permissionMode === "permissionless" || permissionMode === "auto-edit") {
parts.push("--dangerously-skip-permissions");
} else if (permissionMode === "auto") {
parts.push("--permission-mode", "auto");
}

if (config.model) {
Expand Down Expand Up @@ -1065,6 +1067,8 @@ function createClaudeCodeAgent(): Agent {
const permissionMode = normalizeAgentPermissionMode(project.agentConfig?.permissions);
if (permissionMode === "permissionless" || permissionMode === "auto-edit") {
parts.push("--dangerously-skip-permissions");
} else if (permissionMode === "auto") {
parts.push("--permission-mode", "auto");
}

if (project.agentConfig?.model) {
Expand Down