# Permissions

Choose a permission mode, or customize allow, ask, and deny rules for every tool.

## Overview

Permissions are implemented by `packages/nikcli/src/permission/next.ts`. The service combines config rules with persisted project approvals, evaluates wildcard matches, publishes pending requests, and resolves tool execution only after the user replies.

Static

### Config rules

Rules come from `config.permission` and legacy `tools` conversion.

Live

### Pending requests

Ask actions create `per*` requests stored in memory and broadcast through bus events.

Durable

### Always allow

Always replies append allow rules to project storage under `permission/:projectID`.

UI

### Clients

TUI, mobile, and API clients list and reply to pending permission requests.

## Permission modes

The TUI and mobile app surface three named presets plus a derived **Custom** label. Open the TUI command palette and choose **Permissions**, or use the `permission_mode` keybind, to switch modes. A preset writes the current primary agent's `permission` map; agent-level settings take precedence over the global `config.permission` value. The same preset definitions are shared by mobile and web through `permission-presets.ts`.

| Mode | Short label | Behavior |
| --- | --- | --- |
| **Require approval** (`require_approval`) | `Ask` | Ask before every tool action. |
| **Approve for me** (`approve_for_me`) | `Approve` | Automatically allows trusted local work, but asks for shell commands, internet access, external files, sensitive reads, and other high-impact capabilities. |
| **Full access** (`full_access`) | `Full` | Allow tool actions without approval prompts. |
| **Custom** (`custom`) | `Custom` | Shown when individual tool rules no longer match a built-in preset. |

Default mode

An empty permission map is detected as **Approve for me**. This balances uninterrupted local editing and code navigation with explicit approval for riskier actions.

### Approve for me defaults

| Action | Tools and capabilities |
| --- | --- |
| `allow` | Local editing and navigation, tasks and skills, LSP, todos, repository overview, context queries, RAG search/status, and speech. |
| `ask` | `bash`, web fetch/search/code search, external directories, browser and computer control, repository cloning, image generation, memory search, RAG indexing/reset, doom-loop approval, and reads of `*.env` or `*.env.*`. `*.env.example` remains allowed. |
| `deny` | Internal `question`, `plan_enter`, and `plan_exit` permission keys. |

Selecting an individual tool cycles its action through `allow`, `ask`, and `deny`. The mode then appears as **Custom** unless the resulting map still matches one of the presets. A preset is just a starting map; once it lands in `config.permission` (or in an agent-level override), the ruleset evaluator treats it the same as a hand-written `permission` object.

How a preset becomes rules

Each preset is converted to the same `permission` map shape described in [Config forms](#config-forms). `require_approval` writes `{"*": "ask"}` for every known tool; `full_access` writes `{"*": "allow"}`; `approve_for_me` writes the explicit allow, ask, and deny map shown above. Internal keys (`question`, `plan_enter`, `plan_exit`) are always `deny` so the agent cannot bypass the permission pipeline through them.

## Actions and rules

| Action | Behavior |
| --- | --- |
| `allow` | Tool execution proceeds without prompting when permission and pattern match. |
| `deny` | Tool execution fails immediately with `PermissionNext.DeniedError`. |
| `ask` | A pending request is created and execution waits for a reply. |

A rule has `permission`, `pattern`, and `action`. The permission is a tool or capability name. The pattern is tool-specific: command text for bash, file paths for edit/read-style tools, or `*` for capability-wide matching.

## Config forms

The schema accepts three useful forms:

permission config

json 

```
{
  "permission": "ask"
}
```

tool action map

json 

1

2

3

4

5

6

7

8

```
{
  "permission": {
    "bash": "ask",
    "edit": "ask",
    "webfetch": "ask",
    "lsp": "ask"
  }
}
```

pattern map

json 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

```
{
  "permission": {
    "bash": {
      "git status*": "allow",
      "git diff*": "allow",
      "rm *": "deny",
      "*": "ask"
    },
    "edit": {
      "src/**": "ask",
      "package.json": "ask",
      ".env*": "deny"
    }
  }
}
```

Home expansion

Patterns that start with `~/`, `~`, or `$HOME` are expanded to the user's home directory when config is converted to rules.

## Evaluation order

`PermissionNext.evaluate` merges all rulesets and uses the last matching rule. A rule matches only when both the permission wildcard and the pattern wildcard match. If no rule matches, the fallback action is `ask`.

1.  Config permission is converted with `PermissionNext.fromConfig`.
2.  Persisted project approvals are loaded from storage.
3.  Tool-specific extra rules can be merged by callers.
4.  The service uses `findLast`, so later rules override earlier matching rules.
5.  For each requested pattern, deny fails, ask prompts, allow continues.

| Rule set | Persistence | Typical source |
| --- | --- | --- |
| Config rules | Config files and env content | `permission` field. |
| Always approvals | `storage/permission/:projectID` | User clicked or tapped always allow. |
| Extra runtime rules | In-memory for the ask call | Tool or session-specific constraints. |

## Ask and reply lifecycle

1.  A tool calls `ctx.ask` or directly reaches `PermissionNext.ask`.
2.  The request is parsed and assigned a permission id if none was supplied.
3.  Each requested pattern is evaluated against config and stored approvals.
4.  A deny immediately rejects execution.
5.  An ask stores a pending request and publishes `permission.asked`.
6.  The TUI, mobile app, or API client lists pending requests and sends a reply.
7.  The reply publishes `permission.replied` and resolves, rejects, or persists approvals.

| Reply | Effect |
| --- | --- |
| `once` | Resolve only the current pending request. |
| `always` | Append allow rules for the request's `always` patterns, persist them, then auto-resolve same-session requests that now match. |
| `reject` | Reject the request. If feedback text is supplied, the tool sees a correction error. Other pending requests in the same session are rejected too. |

## Always approvals

Always approvals are scoped to the current project id. They are not written into `nikcli.json`. They live in the JSON storage service under a permission key and are loaded into the permission service state when the project instance starts.

Always does not mean global

The persisted approval belongs to the current project. Another repo or project id does not automatically inherit the same always approval.

## Tool integration

Different tools provide different patterns and always scopes.

| Permission | Typical pattern | Notes |
| --- | --- | --- |
| `bash` | Command text or command prefix | Use narrow allow rules for harmless commands and ask/deny for destructive commands. |
| `edit` | File path | Used by write/edit/patch-style tools. |
| `external_directory` | Path outside project | Guards reads/writes outside the current worktree. |
| `webfetch`, `websearch` | URL or wildcard | Network-related tools can be forced to ask. |
| `lsp` | `*` | The LSP tool asks before running language-server operations. |
| `task` | Task or wildcard | Controls delegated work launched by the `task` tool (subagents inherit the parent session's permission map). |
| `generate_image`, `speak` | Provider/model or wildcard | Media generation tools can be separately controlled. |

## API and mobile approvals

Pending permissions are exposed by classic routes and typed HttpApi routes. Mobile routes expose the same request/reply behavior inside the session composer approval controls.

| Surface | Source | Behavior |
| --- | --- | --- |
| Classic API | `server/httpapi/permission.ts` | List pending requests and reply. |
| Typed API | `server/httpapi/permission.ts` | Effect HttpApi list and reply endpoints. |
| TUI | `packages/tui/src/routes/session/permission.tsx` | Shows inline permission prompts in root sessions. |
| Mobile | `server/httpapi/mobile.ts` and mobile composer components | Displays approval bars and sends replies from the phone. |

Terminal

bash 

```
curl http://127.0.0.1:4096/permission

curl -X POST http://127.0.0.1:4096/permission/per_example/reply \
  -H "Content-Type: application/json" \
  -d '{"reply":"once"}'
```

$ shell · paste into your terminal

## Common policies

| Goal | Policy |
| --- | --- |
| Strict by default | Apply the **Require approval** preset (`require_approval`). |
| Balanced local work | Apply the **Approve for me** preset (`approve_for_me`) — the empty-map default. |
| Run unattended in a sandbox | Apply the **Full access** preset (`full_access`); internal keys stay `deny`. |
| Ask before all shell and edits | `bash: ask`, `edit: ask`. |
| Allow harmless git reads | Allow `git status*`, `git diff*`, `git log*`; ask for all other bash. |
| Protect secrets | Deny edit/read patterns such as `.env*` where appropriate. |
| Disable browsing | Set `webfetch` and `websearch` to `deny`. |
| Allow language queries | Set `lsp` to `allow` for trusted projects. |
| Keep delegated work explicit | Set `task` to `ask` so the `task` tool prompts before launching a subagent. |

## Failure modes

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Allowed command still asks | The command pattern does not match exactly or a later ask rule overrides it. | Check wildcard pattern and rule order. |
| Always approval not reused | The later request uses a different pattern or different project id. | Inspect the request's `always` patterns and current project. |
| Reject cancels more than one prompt | Reject intentionally rejects other pending requests in the same session. | Use once/always for approval; reject only when the session should stop that work. |
| Legacy tools config behaves differently | `tools` booleans are converted to permission actions, with write/edit/patch aliases mapped to `edit`. | Migrate to explicit `permission` config. |
| Mobile shows no approval | The request belongs to a child session or was already hydrated/replied. | Check root session aggregation and pending permission list. |

## Source anchors

| Need | File |
| --- | --- |
| Permission service | `packages/nikcli/src/permission/next.ts` |
| Compatibility exports | `packages/nikcli/src/permission/evaluate.ts` |
| Permission schema | `packages/nikcli/src/permission/schema.ts` |
| Config permission schema and legacy tools migration | `packages/nikcli/src/config/config.ts` |
| API routes | `packages/nikcli/src/server/httpapi/permission.ts`, `packages/nikcli/src/server/httpapi/permission.ts` |
| TUI permission mode dialog | `packages/tui/src/component/dialog-permission-mode.tsx` |
| TUI preset definitions and mode detection | `packages/tui/src/util/permission-presets.ts` |
| Mobile preset definitions and mode detection | `packages/mobile/lib/permission-presets.ts` |
| TUI permission prompt | `packages/tui/src/routes/session/permission.tsx` |
| LSP tool permission example | `packages/nikcli/src/tool/lsp.ts` |

## Related

-   [Tools The tool surface that permission rules gate.](/docs/tools)
-   [Agents Per-agent permission overlays and the TUI presets that write them.](/docs/agents)
-   [Configuration Config form, env flags, and the legacy tools → permission migration.](/docs/configuration)
-   [TUI The permission\_mode keybind and the dialog that applies a preset.](/docs/tui)

---

Source: https://nikcli-ai.dev/docs/permissions
