Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Model: openai/gpt-5.4
Generated: 2026-04-01
Book: Claude Code VS OpenCode: Architecture, Design and The Road Ahead
Chapter: 4 — Tool System Design
Token Usage: ~8,200 input + ~1,700 output

4.3 Tool Permissions and Safety

The moment an AI coding agent gains tools, it stops being only a language system and becomes an action system. That transition creates a new security problem. A model that can read files, write files, run shell commands, fetch network resources, or spawn subagents can produce real-world side effects. Tool permissions are therefore not a peripheral UX feature. They are the control plane of the entire runtime.

All three systems in this comparison share the same foundational permission pattern: allow, deny, and ask. This tri-state model is more expressive than a simple boolean. allow means the action can proceed silently, deny means it is blocked automatically, and ask means a human approval checkpoint is required. This is now close to a standard design pattern for agent systems because it separates policy from execution while still leaving room for human intervention.

OpenCode implements this pattern in a relatively compact and elegant way in /packages/opencode/src/permission/next.ts. Permission rules are represented as triples of permission, pattern, and action, where action is a Zod enum over allow, deny, and ask. Rules can be loaded from configuration, merged, and evaluated against a requested permission and a concrete pattern such as a file path. The system uses wildcard matching rather than full semantic analysis. That choice is important. It keeps the mechanism simple, inspectable, and portable.

Rule precedence in OpenCode is resolved through ordered merging and findLast() matching. In effect, later rules override earlier ones, which gives users and higher-precedence configuration sources a straightforward way to specialize or override defaults. OpenCode also normalizes related editing tools under a shared edit permission bucket when determining disabled tools. This is a practical simplification: from a safety perspective, write, edit, patch, and multiedit are variations of the same class of risk.

Another notable OpenCode feature is that permissions are session-aware and can be asked interactively. If a matching rule resolves to ask, the runtime constructs a permission request, publishes a bus event, and pauses until the user replies with once, always, or reject. That creates a clean bridge between static policy and live decision-making. Per-agent permissions also fit naturally into this architecture because the request already carries tool and session metadata, allowing different agents to operate under different behavioral envelopes.

Claude Code takes the same tri-state logic and expands it into a much more layered permission architecture. At the user-facing level, it exposes a four-mode control system commonly described as default, auto, bypass, and plan. These are not just UI presets. They are macro-configurations for how aggressively the runtime should seek approval, how much to trust automated checks, and whether the model is currently allowed to act or only to plan.

The key implementation sits in /src/utils/permissions/permissionSetup.ts, a file well over 1,500 lines long. That size is not accidental complexity alone; it reflects the fact that Claude Code treats permissioning as a first-class product subsystem. The file contains logic for loading rules from settings and CLI arguments, applying them to the runtime context, managing mode transitions, and identifying dangerous permissions that would undermine the classifier layer.

Two especially important additions appear beside the rule engine. First, Claude Code has a bash command classifier. Second, it has an ML-based YOLO classifier in yoloClassifier.ts. The bash classifier and dangerous-pattern logic look for commands or rule patterns that are broad enough to smuggle arbitrary execution, such as permitting interpreters or nested shell launches too loosely. The YOLO classifier adds a probabilistic policy layer: instead of matching only static rules, it inspects the action and context to predict whether a tool call should be blocked or require review.

This is a major architectural difference from OpenCode. OpenCode’s permission system is mostly symbolic: patterns, rules, precedence, and user approval. Claude Code keeps that symbolic layer but adds statistical judgment on top. The benefit is lower friction. The cost is greater complexity and less transparency. Yet in a commercial product this tradeoff is often justified, because the goal is not just correctness but throughput under realistic user impatience.

Claude Code also contains explicit dangerous-pattern detection. In permissionSetup.ts, rules that broadly allow Bash, PowerShell, or Agent spawning can be flagged as unsafe for auto mode because they would bypass classifier checks. This is a crucial insight: permission rules themselves can become vulnerabilities if they are too permissive. A safe permission system must therefore validate not only actions but also the rules used to authorize actions.

OMO largely inherits OpenCode’s base permission model, but it does not stop there. Its added hooks create a second defensive layer around tool execution. In src/plugin/tool-execute-before.ts, OMO runs a sequence of pre-tool hooks including a write-existing-file guard, a question label truncator, and a rules injector. In practice, this means OMO can impose additional checks or mutate tool invocation context before the underlying tool runs. That is a different style of safety engineering from Claude Code’s classifier-heavy design. It is more hook-driven and compositional.

OMO also applies post-tool safety and hygiene measures. Its output truncation hook can shrink oversized results after execution, and its metadata store restores execution metadata that OpenCode’s plugin wrapper would otherwise discard. Although these are not “permissions” in the narrow sense, they are part of the same safety envelope: controlling what tools may do, what they may return, and how much unbounded information they may inject back into the conversation.

The broader strategic lesson comes from Anthropic’s reported result that OS-level sandboxing can reduce permission prompts by roughly 84%. The key idea is simple: if the runtime can safely constrain what a command can affect at the operating-system level, then fewer actions require human review. Bubblewrap on Linux and Seatbelt on macOS are examples of such sandboxing technologies. They restrict filesystem, process, and network reach so that a large class of tool calls becomes mechanically safer.

This is an important shift in design philosophy. The best way to reduce permission fatigue is not to nag the user less. It is to make more actions intrinsically safe. If the environment itself prevents dangerous side effects, the agent can be granted more autonomy without increasing real risk. In security engineering terms, this moves protection from policy-only enforcement toward capability confinement.

Across the three systems, we therefore see a layered model emerging. First comes the symbolic permission rule: allow, deny, ask. Next comes runtime context: which agent, which file, which command, which session. Then come secondary defenses: hooks, dangerous-pattern detectors, classifiers, and sandboxing. The strongest systems do not rely on any single layer.

The design conclusion is straightforward. A serious coding agent should not ask for permission only because the prompt says so. It should have a formal permission model, explicit precedence rules, path or command pattern matching, support for human escalation, and, ideally, environmental confinement. Safety is not one feature. It is the combined architecture around tool execution.