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.
Config rules
Rules come from config.permission and legacy tools conversion.
Pending requests
Ask actions create per* requests stored in memory and broadcast through bus events.
Always allow
Always replies append allow rules to project storage under permission/:projectID.
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. |
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.
Each preset is converted to the same permission map shape described in
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": "ask"
} {
"permission": {
"bash": "ask",
"edit": "ask",
"webfetch": "ask",
"lsp": "ask"
}
} {
"permission": {
"bash": {
"git status*": "allow",
"git diff*": "allow",
"rm *": "deny",
"*": "ask"
},
"edit": {
"src/**": "ask",
"package.json": "ask",
".env*": "deny"
}
}
}
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.
- Config permission is converted with
PermissionNext.fromConfig. - Persisted project approvals are loaded from storage.
- Tool-specific extra rules can be merged by callers.
- The service uses
findLast, so later rules override earlier matching rules. - 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
- A tool calls
ctx.askor directly reachesPermissionNext.ask. - The request is parsed and assigned a permission id if none was supplied.
- Each requested pattern is evaluated against config and stored approvals.
- A deny immediately rejects execution.
- An ask stores a pending request and publishes
permission.asked. - The TUI, mobile app, or API client lists pending requests and sends a reply.
- The reply publishes
permission.repliedand 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.
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. |
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"}' 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 |