Permissions
Approval modes and the permission round-trip
Modes
type PermissionMode = "read-only" | "ask" | "accept-edits" | "full-auto";| Mode | Meaning |
|---|---|
read-only | The agent may not change anything. Without a handler, requests are auto-denied. |
ask | Privileged actions surface as permission requests. |
accept-edits | File edits auto-approved; shell and other privileged actions still ask. |
full-auto | Maps to each backend's bypass mode (Claude bypassPermissions, Codex approval_policy: never). Only expose in trusted contexts. |
Default: "ask" when you pass onPermissionRequest, otherwise "accept-edits".
The round-trip
const session = await createAgent({
provider: "claude",
permissionMode: "ask",
onPermissionRequest: async (req) => {
// req: { id, kind, title, command?, cwd?, changes?, raw? }
if (req.kind === "shell") {
const ok = await myUi.confirm(`Run: ${req.command}?`);
return { behavior: ok ? "allow" : "deny", scope: "once" };
}
return { behavior: "allow" };
},
});type PermissionKind = "shell" | "file_edit" | "file_read" | "network" | "mcp" | "other";
interface PermissionRequest {
id: string;
kind: PermissionKind;
title: string;
command?: string; // for shell
cwd?: string;
changes?: FileChange[]; // for file edits
raw?: unknown; // the native payload
}
interface PermissionResponse {
behavior: "allow" | "deny";
scope?: "once" | "session";
message?: string; // shown to the agent on deny
updatedInput?: unknown; // rewrite the action before allowing
}Without a handler
Requests surface as permission.requested events and stay pending until you call session.respond(requestId, response) — useful when approval happens in a different part of your app (IPC, HTTP, a UI thread). In read-only mode they auto-deny.
session.on("permission.requested", ({ request }) => {
showApprovalDialog(request); // later: session.respond(request.id, { behavior: "allow" })
});Every resolution — yours or automatic — emits permission.resolved.
Per-provider notes
- codex —
askinherits Codex's sandbox semantics: in-workspace file edits are auto-approved natively and never reach your handler; shell commands outside the sandbox do. - claude — full native round-trip, including deny messages and mid-session mode changes.
- opencode — mapped to opencode's permission config (
bash/editask/allow/deny) and answered over its HTTP API. - pi — Pi has no approval protocol, so Klein Kit injects a permission-gate extension into the Pi process that blocks tool calls and asks through RPC. Reads always auto-allow.
- cursor — no approval protocol in print mode;
respond()throwsCapabilityUnsupportedError.read-onlyis best-effort, not a security boundary.
Check session.capabilities.permissionCallback ("native" | "emulated" | "none") before relying on the round-trip. See Providers for the full matrix.