Skip to content

Permission modes

A permission mode says how much pi may do before it asks you. Each thread has one mode at a time, and you can change it while the thread is open. Every mode but default is a preset Enso lays over your permission policy for that thread; the modes follow Claude Code’s:

Mode What changes
default The policy in permissions.json, as written.
acceptEdits Edits and writes inside the project run without asking.
plan Read and explore; the edit and write tools are denied.
bypassPermissions Everything runs without asking, except what your own rules deny or ask.
auto Edits run as in acceptEdits; a classifier model answers what would ask.

When pi needs to ask, the question appears in the chat. See Questions and dialogs.

Your permission policy lives in ~/.enso/permissions.json (ENSO_HOME/permissions.json). Its permission block names each tool or surface and what happens to it: allow, ask or deny, or a map from pattern to one of those in which the last matching pattern wins, for example "bash": { "*": "ask", "npm publish*": "deny" }. A deny rule denies in every mode. A new ENSO_HOME starts with Enso’s default policy: read, grep, find, ls, ask_user and web_search run without asking; edits and writes ask; shell commands ask, except 29 read-only ones such as git status and ls; no git commit --no-verify, no git commit -n and no git push --delete; *.env and *.env.* files are denied to every tool; anything outside the project asks. The launch log says which file was used.

In auto, a question pi would ask you goes first to a classifier: the thread’s own model, asked whether the action stays within what you asked for. It allows the action or refuses it with a reason pi reads. When it cannot answer (no model, no credentials, no reply within 10 seconds, a reply it cannot read) you are asked instead. A rule you wrote to ask, such as "git push*": "ask", always reaches you, and so does a file path or a directory outside the project that the classifier would allow.

  • The mode selector sits above the composer. Pick a mode to change it.
  • Or type /mode in the chat to choose from a list, or /mode <name>. /default, /acceptEdits, /plan, /bypassPermissions and /auto switch directly.
  • A new thread’s mode is picked in Start a session.

To see it: open a thread from the rail.

The mode line and the mode selector

  1. The mode, as a line in the chat. Every change adds one, so the transcript shows when each mode started.
  2. The mode selector. It lists the modes this thread offers.

Read the mode lines with the tool calls around them. A run of edits that went through without a question is expected under acceptEdits, and worth a look under default.

A change you make is not applied by the click. The server asks the thread to change, and the new mode arrives back on the thread’s feed like any other event. So the selector always shows the mode pi is actually in, and a change refused because the thread is busy leaves it as it was.

A new thread shows the mode as unknown until its first prompt has run. It starts in default.

  • A mode is not a guard. The guards apply in every mode, bypassPermissions included. See Guards and receipts.
  • bypassPermissions still asks for a command run through a wrapper (bash -c, sudo, eval) and for one the permission engine cannot parse.
  • A shell command the policy allows may redirect its output into a file inside the project (cat a > b) without asking. Writes outside the project still ask.
  • The terminal’s print variant (bun run enso -p "…") has no permission modes. There is nobody to ask, so the permission engine would refuse every ask. Only Enso’s guards apply there, and bash is not confined.
  • Enso does not read Claude Code’s permission rules (~/.claude/settings.json). Copy the ones you want into permissions.json yourself.
  • An ENSO_HOME from before #496 held its rules as allow, deny and ask lists. The first launch after moves that file aside as permissions.json.picc-backup and seeds the default policy; the old rules are not carried over.
  • An edit to permissions.json applies at the next tool call, in running threads too. A file that is not plain JSON stops the launch, and the error names the file; comments are not allowed. Break it while a thread runs and that thread reads an empty policy: every call asks, and the deny rules are gone until the file is fixed.
  • A trusted project’s own .pi/ settings for the permission engine, and its .pi/agents/ files, are read over your policy and can loosen it. In the web app every project counts as trusted; in the terminal, pi’s own trust question decides.
  • A mode the thread does not offer is refused, and so is a change while the thread is running.