Read, write, and exec are tiers
Nia’s next mistake would be to infer behavior from a tool’s name. A tool called read is not always read-tier. A tool carried through write is not always write-tier. The declaration can be a function of arguments.
A tier is a tool’s approval classification. It is not a sandbox, a syscall monitor, or proof that the implementation has no other effects.
Learn the baseline matrix first
The resolver ranks read below write below exec. With no explicit policy or active override changing the decision, the matrix is exact:
| Approval mode | Read tier | Write tier | Exec tier |
|---|---|---|---|
always-ask | allow | prompt | prompt |
write | allow | allow | prompt |
yolo | allow | allow | allow |
The name always-ask therefore does not mean every call prompts. It automatically admits read-tier calls at this baseline layer.
The schema default for tools.approvalMode is yolo. The wrapper also falls back to yolo when the execute-time settings context supplies no mode. That is a source default, not a statement about a reader’s installation. A tool with no approval declaration defaults to exec, not read.
Recorded: Approval Desk exercised all nine combinations through the actual resolveApproval() and requiresApproval() functions. The cases are approval-always-ask-read, approval-always-ask-write, approval-always-ask-exec, approval-write-read, approval-write-write, approval-write-exec, approval-yolo-read, approval-yolo-write, and approval-yolo-exec. They classified calls; they did not execute tools or show dialogs.
An omitted declaration is a useful counterexample
Cover the expected result for approval-undeclared-exec. It supplies the fictional tool name seed_note, no declaration, and mode write.
Prediction: does the absence of an execution-related name make this an automatically allowed call?
Recorded answer: the result was policy: "prompt", tier: "exec", override: false, source: "mode". Omission is deliberately conservative. requiresApproval() returned required: true; that is still a classification result, not evidence that a UI appeared.
The same default applies to an unannotated MCP tool at this resolver. An MCP-looking name does not itself establish a lower tier. The existing approval.test.ts tests both unannotated and explicitly annotated MCP subjects.
Read tier does not mean universally read-only
Several supplied declarations make the limit concrete:
| Source-backed operation | Declaration | Boundary to retain |
|---|---|---|
Ordinary ReadTool path | Usually read | URL reads can make network requests; some specialized reads escalate. |
ReadTool or GrepTool targeting ssh:// | exec | The higher tier does not grant remote access. |
HubTool peer send, inbox, waits, and job cancel | read for these declared forms | Messaging, consuming an inbox, or cancelling a job can change agent/job state. |
HubTool process send with a process name and no peer target | exec | Process input is classified differently from a peer message. |
Ordinary filesystem WriteTool target | write | The tier is not a workspace-containment guarantee. |
ManageSkillTool | write | Its managed-skill storage is not simply the current workspace. |
EvalTool | exec | A harmless-looking cell does not lower this static declaration. |
These rows are source examples, not new executed workbook scenarios. The hubApproval() implementation, rather than its broad comment about read-only operations, establishes the classifications. HubTool.execute() then routes those operations to messaging and lifecycle functions.
Likewise, ReadTool.approval() returns read for ordinary HTTP URL reads, while its execution path can reach executeReadUrl() or fetchReadUrl(). Local-looking or read-tier work is not a general no-network promise.
A declaration can also distinguish handler-backed paths and session artifacts. Later chapters follow those paths without pretending that read-tier artifact work has zero effects.
Use tiers to predict a gate, not to certify a program
A capable operator can use the matrix to predict the default policy. To assess the effect, the operator still needs the actual implementation and destination. A tool author can declare a tier incorrectly, and trusted in-process code can do work outside a particular wrapped call.
Paper checkpoint: predict the baseline policy for an unannotated seed_note in write mode, then for a read-tier peer message in always-ask. Worked answer: prompt, then allow. Neither answer establishes what a real implementation would change or disclose.
Failure boundary: the matrix is the fallback after the resolver’s higher-precedence branches. Do not apply it before checking explicit denies, explicit tool policies, overrides, or the effective launch mode.
Source anchors: packages/coding-agent/src/tools/approval.ts — normalizeDecision, resolveToolTier, APPROVAL_MODE_MAX_TIER, resolveApproval; packages/coding-agent/src/config/settings-schema.ts — tools.approvalMode; packages/coding-agent/src/tools/hub/index.ts — hubApproval, HubTool.execute; packages/coding-agent/src/tools/read.ts — ReadTool.approval, #executeInner; packages/coding-agent/src/tools/grep.ts — GrepTool.approval; packages/coding-agent/src/tools/manage-skill.ts — ManageSkillTool; packages/coding-agent/src/tools/eval.ts — EvalTool.
Tool permissions and approvals · Source chapter: permissions/read-write-and-exec-are-tiers. Original evidence remains scoped to its recorded snapshot.