# Tool approvals

> The rules that decide if a tool call needs your approval in the terminal and desktop apps, and what each answer does.

Tool approvals decide who approves a tool call before it runs. **Ask**, the default in both apps, asks you. **Model decision** lets the decision model approve clearly intended calls and block clearly risky ones. When it is unsure, it asks you. **Full access** runs every call without asking. Typing into a terminal you opened still asks. The cards and keys are on [terminal approvals](https://zidane.si/docs/tui-approvals.md) and [desktop approvals](https://zidane.si/docs/desktop-approvals.md).

## How a call is decided

The host checks each tool call in order:

1. Tool approvals is Full access: the call runs.
2. The tool is [implicitly safe](#tools-that-never-ask): the call runs.
3. The call matches the project's [safelist](#safelist), or you approved it for this session: the call runs.
4. Tool approvals is Model decision and the decision model is sure: it approves or blocks the call.
5. Otherwise the run pauses for your answer.

Pick the mode in Settings › Agent › Guardrails › Tool approvals in the terminal, and in Settings › Agent › Approvals & safety › Tool approvals in the desktop app.

## Tools that never ask

| Why | Tools |
| - | - |
| Read files | `read_file`, `list_files`, `glob`, `grep` |
| Track work | `todowrite`, `todoread`, `skills_use` |
| They ask you anyway | `ask_user`, `present_plan` |
| Read the session's terminals | `terminal_list`, `terminal_read` |
| Use the dev browser | Every browser tool except `browser_evaluate`. `browser_open` and `browser_navigate` skip the question only for a loopback address such as `localhost`. |
| Read the chat a side session was opened on | `read_parent_session` |

`terminal_write` types into a shell, so it asks like `shell`. `skills_read` and `skills_run_script` also ask.

## Your answers

| Answer | Does | Terminal | Desktop |
| - | - | - | - |
| Once | Runs this call | accept once | **Allow once** |
| For the session | Runs this and every matching call in this session | accept for session | File edits only (**Allow for session**) |
| Remember | Runs it and adds a safelist entry for the project | accept + remember | **Always allow** |
| Deny | Blocks the call. The agent reads that you refused. | deny | **Deny** |

`edit`, `multi_edit` and `write_file` show the diff first. For an edit with several hunks, you tick the ones to apply. The agent learns which you applied.

## Safelist

The safelist is a per-project list in `projects.json` in your config folder ([where](https://zidane.si/docs/files.md#config-and-data-files)). The project is the git root, so subfolders and every worktree of a repo share one list.

| Entry | Matches |
| - | - |
| `grep` | Every call to that tool |
| `shell:git:*` | Shell commands whose first word is `git` |
| `browser_navigate:example.com` | That browser tool on that host (port included) |

```json title="projects.json"
{
  "/Users/me/my-app": { "safelist": ["grep", "shell:git:*", "shell:bun:*"] }
}
```

A shell command with several parts (`&&`, `||`, `;`, `|`, `$(...)`, subshells) skips the question only when every part is on the list. So **Remember** on `cd src && bun test` adds `shell:cd:*` and `shell:bun:*`. A command zidane cannot parse always asks. Review entries in the desktop safelist panel (Settings › Agent › Approvals & safety) or edit `projects.json` by hand.

## Plans and questions

| Tool | You can |
| - | - |
| `present_plan` | Approve, revise with a note, or reject. A rejection stops the run. |
| `ask_user` | Pick options, type an answer, or give a secret ([below](#secrets)) |

### Interaction modes

**Interaction mode** sets what happens when the agent calls them. It is in Settings › Agent › Guardrails in the terminal, and in Settings › Agent › Defaults in the desktop app.

| Mode | The agent |
| - | - |
| Ask normally (`ask`, default) | Waits for your answer |
| Skip interactions (`skip`) | Gets an automatic answer and decides from the repository. Plans are approved, yes/no questions get yes, choices get the first option, and text questions get no answer. |
| Disable tools (`disabled`) | Has no `present_plan` or `ask_user` tool |

Switching to Skip answers the questions already waiting. Prompts from `/` commands and extensions ask in every mode.

### Secrets

With **Secrets** on (Settings, Guardrails group, off by default), `ask_user` can ask you for an API key or a token. You type it in a password field and pick how long zidane keeps it: 1 hour, 24 hours (the default), 7 days or until you revoke it. Then press **Save securely**, or **Decline**.

The value never goes into the chat. zidane keeps it in a private file for the project: `~/.zidane/secrets/<project-hash>/` on your computer, or `/tmp/zidane-secrets/<project-hash>/` inside a Docker, E2B or Daytona sandbox. The agent's commands read it at `$ZIDANE_SECRETS/NAME`. Tool output shows a saved value as `[secret:NAME]`, and the transcript shows "Saved NAME, expires in 24 hours". A value shorter than 4 characters is not hidden. A secret is removed when it expires, even if nothing reads it.

When the agent asks again for a saved name, zidane uses the saved one without asking. In Skip mode, and in Slack and GitHub channels, nobody can type a value: zidane uses a saved one or declines. A value only travels over the local app, TLS, or a connection from the server machine itself. Revoke secrets in the Secrets panel of the desktop app or on the Secrets page of the terminal app's settings. The full rules are in [INTERACTIONS.md](https://github.com/Tahul/zidane/blob/main/docs/INTERACTIONS.md#secrets).

## With several windows

With [the daemon](https://zidane.si/docs/daemon.md), every window shows approvals and questions, `zidane attach` included, and the first answer wins. Without it, only the window that started the run can answer. A browser connected through a zidane-server answers them with its `chat` token ([UI kit adapters](https://zidane.si/docs/ui-adapters.md)).

## Limits and the sandbox

**Run limits** (both apps) stop a run:

| Setting | Stops a run after | Default |
| - | - | - |
| Max turns per run (`maxTurns`) | That many turns | Unlimited |
| Max cost per run (`maxCostUsd`) | The estimated cost passes the amount | Unlimited |
| Max tokens per run (`maxTotalTokens`) | That many tokens | Unlimited |
| Max run time (`maxWallMinutes`) | That much time | Unlimited |

SDK options and errors: [run-level budgets](https://zidane.si/docs/errors.md#run-level-budgets).

**Sandbox shell commands** confines the agent's commands and edits to the project and hides your credential files ([OS sandbox](https://zidane.si/docs/execution-contexts.md#os-sandbox)). With Full access and the sandbox on, the agent runs without asking and stays inside the project.

## Headless runs and editors

`zidane run --json` and `--stream-json` never ask. Every tool call runs ([headless approvals](https://zidane.si/docs/cli-headless.md#approvals)). Through [ACP](https://zidane.si/docs/acp.md), zidane asks your editor for permission.
