Skip to content
nikcli
Docs
Documentation

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.

ModeShort labelBehavior
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

ActionTools and capabilities
allowLocal editing and navigation, tasks and skills, LSP, todos, repository overview, context queries, RAG search/status, and speech.
askbash, 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.
denyInternal 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. 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

ActionBehavior
allowTool execution proceeds without prompting when permission and pattern match.
denyTool execution fails immediately with PermissionNext.DeniedError.
askA 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
{
  "permission": {
    "bash": "ask",
    "edit": "ask",
    "webfetch": "ask",
    "lsp": "ask"
  }
}
pattern map
json
{
  "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 setPersistenceTypical source
Config rulesConfig files and env contentpermission field.
Always approvalsstorage/permission/:projectIDUser clicked or tapped always allow.
Extra runtime rulesIn-memory for the ask callTool 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.
ReplyEffect
onceResolve only the current pending request.
alwaysAppend allow rules for the request's always patterns, persist them, then auto-resolve same-session requests that now match.
rejectReject 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.

PermissionTypical patternNotes
bashCommand text or command prefixUse narrow allow rules for harmless commands and ask/deny for destructive commands.
editFile pathUsed by write/edit/patch-style tools.
external_directoryPath outside projectGuards reads/writes outside the current worktree.
webfetch, websearchURL or wildcardNetwork-related tools can be forced to ask.
lsp*The LSP tool asks before running language-server operations.
taskTask or wildcardControls delegated work launched by the task tool (subagents inherit the parent session's permission map).
generate_image, speakProvider/model or wildcardMedia 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.

SurfaceSourceBehavior
Classic APIserver/httpapi/permission.tsList pending requests and reply.
Typed APIserver/httpapi/permission.tsEffect HttpApi list and reply endpoints.
TUIpackages/tui/src/routes/session/permission.tsxShows inline permission prompts in root sessions.
Mobileserver/httpapi/mobile.ts and mobile composer componentsDisplays 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"}'

Common policies

GoalPolicy
Strict by defaultApply the Require approval preset (require_approval).
Balanced local workApply the Approve for me preset (approve_for_me) — the empty-map default.
Run unattended in a sandboxApply the Full access preset (full_access); internal keys stay deny.
Ask before all shell and editsbash: ask, edit: ask.
Allow harmless git readsAllow git status*, git diff*, git log*; ask for all other bash.
Protect secretsDeny edit/read patterns such as .env* where appropriate.
Disable browsingSet webfetch and websearch to deny.
Allow language queriesSet lsp to allow for trusted projects.
Keep delegated work explicitSet task to ask so the task tool prompts before launching a subagent.

Failure modes

SymptomLikely causeFix
Allowed command still asksThe command pattern does not match exactly or a later ask rule overrides it.Check wildcard pattern and rule order.
Always approval not reusedThe later request uses a different pattern or different project id.Inspect the request's always patterns and current project.
Reject cancels more than one promptReject 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 differentlytools booleans are converted to permission actions, with write/edit/patch aliases mapped to edit.Migrate to explicit permission config.
Mobile shows no approvalThe request belongs to a child session or was already hydrated/replied.Check root session aggregation and pending permission list.

Source anchors

NeedFile
Permission servicepackages/nikcli/src/permission/next.ts
Compatibility exportspackages/nikcli/src/permission/evaluate.ts
Permission schemapackages/nikcli/src/permission/schema.ts
Config permission schema and legacy tools migrationpackages/nikcli/src/config/config.ts
API routespackages/nikcli/src/server/httpapi/permission.ts, packages/nikcli/src/server/httpapi/permission.ts
TUI permission mode dialogpackages/tui/src/component/dialog-permission-mode.tsx
TUI preset definitions and mode detectionpackages/tui/src/util/permission-presets.ts
Mobile preset definitions and mode detectionpackages/mobile/lib/permission-presets.ts
TUI permission promptpackages/tui/src/routes/session/permission.tsx
LSP tool permission examplepackages/nikcli/src/tool/lsp.ts