Pi is not the four-tool, no-MCP harness the ranking comparisons describe: v0.99.0 added MCP, codemode and tool search on 2026-09-29, and v1.5.0 (2026-11-02) is the version to pin because it fixes a saved Do not trust decision being ignored for folders containing only AGENTS.md. Its default enabled tools are still read, bash, edit and write, and codemode and tool_search switch on only when an MCP server needs them. Pi's own security doc says it does not ask for approval before every tool call, and OpenCode V2 (v2.2.5) also starts from an allow-everything policy in which only .env reads ask. Claude Code is the only one of the three that checks tool calls by default (interactive sessions start in auto mode, where a classifier reviews actions, and Manual prompts are one setting away) and ships an OS-level sandbox for shell commands. Pi has the strongest repository gate of the three: nine protected resource types wait for a trust decision, headless runs skip them under the default, and the default itself can only be set at user level; OpenCode V2 has no documented trust gate at all. Pi also has a single offline switch (--offline or PI_OFFLINE=1), while OpenCode V2 has none and Claude Code's non-essential traffic switch leaves two paths open. For an air-gapped team, run Pi or OpenCode inside a container or VM; start by writing Pi's four-key settings file with defaultProjectTrust set to never.
The pages ranking for "pi coding agent vs claude code" describe a harness that no longer exists. They say Pi has four tools and no MCP. Pi v0.99.0 added MCP, codemode and tool search as built-in extensions on 2026-09-29, and has shipped v1.0.0 through v1.5.0 (2026-11-02) since, and the repository now sits at about 120,000 stars under a new organisation (earendil-works/pi, MIT, TypeScript).
If you are the platform or security engineer choosing a terminal coding harness for developers at a bank, an insurer or a defence supplier, the feature list is the wrong comparison anyway. The question that decides whether a harness can go on a regulated workstation is what it lets you lock down, and on that question the three tools differ more than their feature lists suggest.
This post compares Pi v1.5.0, OpenCode V2 at v2.2.5 and Claude Code's documented behaviour on lockdown controls. Every claim comes from the documentation and source at those versions; nothing here was run. The short verdict: Pi is the easiest of the three to lock down for local models, but like OpenCode it executes tool calls without asking, so its real boundary has to be a container or VM.
What changed in Pi coding agent from v0.99.0 to v1.5.0
The release cadence explains why the ranking pages are stale. v0.99.0 (2026-09-29) added MCP; v1.0.0 followed on 2026-10-01, v1.0.4 on 2026-10-05, then v1.1.0, v1.2.0, v1.3.0 and v1.4.0 through October, and v1.5.0 on 2026-11-02.
The v0.99.0 release note is specific: MCP servers over stdio or streamable HTTP, with OAuth, come from mcp.json (global, or per project once trusted) and are managed with /mcp and pi mcp add|remove|list|login|logout. Codemode runs model-written JavaScript in a QuickJS sandbox that calls Pi's tools. v1.0.4 added --no-mcp and * patterns in --tools.
Three details to get right before comparing anything:
defaultTools changes them. powershell, grep, find and ls exist but are off."autoEnableCodemode": false beside mcpServers to stop codemode switching on automatically.The v1.5.0 release also carries a security fix that sets your minimum version. Issue #10947, closed on 2026-10-29: a saved "Do not trust" decision was ignored for projects without trust-requiring resources, such as a folder containing only AGENTS.md, so Pi auto-trusted them, ctx.isProjectTrusted() returned true and package commands treated them as trusted. Any Pi older than v1.5.0 has that bug. Pin v1.5.0, and stage @earendil-works/pi-coding-agent from an internal npm mirror rather than letting developers pull whatever is current.
The same release removed Pi's native OS sandbox extension example. At v1.5.0 the documented isolation options are a container, a VM, or the Gondolin micro-VM extension example.
Pi coding agent MCP vs Claude Code and OpenCode
All three harnesses support MCP. For a locked-down team the differences sit in project configuration and exposure, not in the protocol.
Codemode in Pi and Code Mode in OpenCode are both forms of the code-execution-over-MCP approach: the model writes a script that calls tools instead of receiving every tool schema up front. The code execution with MCP pattern explains why that shrinks context.
Pi's four exposure modes
Each Pi server gets an exposure, overridable per tool with toolExposure, where exact names beat * patterns: Tools are named mcp__<server>__<tool>, which makes CLI filtering exact. --exclude-tools 'mcp__*' removes every MCP tool; --tools read,bash,codemode,'mcp__radius__*' keeps one server. Persistently, "extensions": ["-builtin:mcp"] in settings disables MCP entirely. The QuickJS sandbox needs one honest sentence. Pi's codemode doc says scripts have no Node APIs, file system, network or timers, and reach the outside world only through tools. One of those tools is bash. QuickJS limits what the script can do directly; it is not a boundary on what the agent can do. Pi stores MCP OAuth tokens in ~/.pi/agent/mcp-auth.json. Remember that path when you decide what to mount into a container.
codemode(default): callable from codemode scripts, but neither declared to the model nor listed in the codemode description.deferred: not declared untiltool_searchloads a match for the next model call.direct: declared to the model like a built-in tool, and also callable from codemode.hidden: registered but unreachable.
| Pi v1.5.0 | Claude Code | OpenCode V2 (v2.2.5) | |
|---|---|---|---|
| Project config | .pi/mcp.json | .mcp.json | project opencode.json(c), mcp.servers |
| Project servers gated by | Project trust, before the file is read | Approval prompt in interactive sessions only | No documented gate; mcp.use policy can deny |
| Headless behaviour | Skips project servers under default trust | claude -p loads project servers without asking | Loads project config |
| Default exposure | codemode (not declared to the model) | Tool search defers MCP tools | Code Mode per server |
| Turning project servers off | --no-mcp per run | --strict-mcp-config uses only --mcp-config servers | OPENCODE_DISABLE_PROJECT_CONFIG=1; mcp.use policy deny |
Does Pi, Claude Code or OpenCode ask before running commands
This is where the three diverge most.
{
"$schema": "https://opencode.ai/config.json",
"permissions": [
{ "action": "shell", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "git status *", "effect": "allow" },
{ "action": "shell", "resource": "git diff *", "effect": "allow" },
{ "action": "shell", "resource": "git push *", "effect": "deny" },
{ "action": "external_directory", "resource": "*", "effect": "ask" }
]
}Pi
Pi's security doc opens with the sentence a reviewer needs: Pi can read, change and execute files with the permissions of the account that started it, and it does not ask for approval before every tool call. It goes further: watching the transcript, using project trust and reviewing changes do not create a security boundary. Running entirely inside a container, VM or sandbox is, in its words, usually the strongest practical option. Per-call approval exists as an extension. The permission-gate.ts example confirms bash commands matching patterns such as rm -rf, sudo and chmod 777, and blocks them when there is no UI. Because every MCP call passes through the same tool pipeline, a gate extension covers MCP tools too, and Pi exposes MCP annotation hints (readOnlyHint, destructiveHint) for gates to act on. A pattern gate is a speed bump, not containment.
Claude Code
Interactive Claude Code sessions with no configured mode start in auto mode, where a background classifier reviews actions instead of prompting you. Manual mode (default) prompts on first use of each tool; claude -p and SDK runs start there and deny calls that would need approval, and permissions.defaultMode in managed settings makes it the start mode for everyone. The six modes are default, acceptEdits, plan, auto (a background classifier decides), dontAsk (auto-denies anything that would prompt) and bypassPermissions, which the docs scope to isolated containers and VMs only. The built-in sandbox wraps shell commands in an OS-enforced boundary: writes limited to the working directory, temp and added directories, and network through a local proxy whose allowed domains start empty. Reads still cover most of the machine, including ~/.ssh and ~/.aws/credentials, unless you set denyRead. Native Windows runs unsandboxed.
OpenCode V2
OpenCode V2's documented base policy is { action: "*", resource: "*", effect: "allow" } plus ask rules for .env reads. Because no default rule asks for external_directory, reads and edits outside the project proceed without a prompt. The rule engine is good, though: ordered rules, last match wins, and the docs warn that shell runs with the host user's filesystem, process and network authority, so a narrow allowlist beats patterns that try to recognise every dangerous command. This is V2 syntax; V1 uses permission and bash, and OpenCode 1 and 2 share the opencode command, with the V2 installer replacing the V1 binary. Broad rules come first because the last match wins. A repository's own config loads without a gate, so these rules are a developer default rather than an organisation control. The control a repository cannot loosen is a policy: binary allow or deny statements on tool.use, mcp.use, plugin.use and others, which never prompt, only tighten, and override project configuration when set globally or through managed config in /etc/opencode/. mcp.use, skill.use and plugin.use arrived in v2.2.3 on 2026-10-30, so older V2 builds lack them.
| Pi v1.5.0 | Claude Code | OpenCode V2 (v2.2.5) | |
|---|---|---|---|
| Default | No approval before tool calls | Interactive: auto mode, a classifier reviews actions; -p: Manual, unapproved calls denied | Allow everything; only .env reads ask |
| Built-in rule engine | None; per-call gates via extensions | Six permission modes plus allow and deny rules | Ordered allow, ask, deny rules; last match wins |
| Built-in OS sandbox | None (example removed in v1.5.0) | Yes, for Bash: Seatbelt on macOS, bubblewrap on Linux and WSL2 | None; shell runs with the host user's authority |
Untrusted repos: what Pi, Claude Code and OpenCode load
A coding harness opened in a hostile repository is the threat model that produced a run of trust-dialog CVEs, which our post on files coding agents auto-load from untrusted repos catalogues. Here is how the three gate a fresh clone.
Pi: the strongest gate, with two documented holes
Pi requires a trust decision when it finds any of these from the working directory: .pi/settings.json, .pi/mcp.json, .pi/extensions, .pi/skills, .pi/prompts, .pi/themes, .pi/SYSTEM.md, .pi/APPEND_SYSTEM.md, and project .agents/skills. A bare .pi directory does not trigger it. The decision order is a CLI --approve or --no-approve override, then user-level extensions handling the project_trust event, then a saved decision in ~/.pi/agent/trust.json, then defaultProjectTrust, which defaults to "ask". That setting can only be set in agent-directory (user-level) settings, so a repository cannot grant itself trust. Print, JSON and RPC modes cannot show the prompt, so under "ask" or "never" they skip protected resources. Headless Pi fails closed, the opposite of claude -p. The two holes are documented. Context files (AGENTS.override.md, AGENTS.md, CLAUDE.md) load regardless of trust unless you pass -nc (--no-context-files). And Pi reads the project sessionDir setting before trust resolves. Pi also says plainly that trust does not limit what tool calls can access.
Claude Code: interactive only
Claude Code shows its workspace trust dialog when you start an interactive session in an untrusted folder, and project .mcp.json servers get their own prompt. Project permissions.allow and additionalDirectories apply only after trust. A claude -p run never shows the dialog: it connects project .mcp.json servers without asking and runs hooks from project settings, though project allow rules stay off. An SDK session does the same when its settingSources include project settings.
OpenCode V2: no gate
No trust feature appears in the v2.2.5 docs or in the core and CLI source. Project opencode.json(c) and .opencode/opencode.json(c) load automatically, as does every AGENTS.md from the workspace up to the project root. V2 recognises AGENTS.md only, not CLAUDE.md, and reads .claude only for skills. The switch is OPENCODE_DISABLE_PROJECT_CONFIG=1, which also skips project AGENTS.md discovery.
| Repo file | Pi v1.5.0 | Claude Code | OpenCode V2 |
|---|---|---|---|
| Harness settings | After trust | After trust (interactive) | Loads |
| Project MCP servers | After trust | Prompt (interactive); loads under -p | Loads; mcp.use policy can deny |
| AGENTS.md / CLAUDE.md | Loads; -nc disables | CLAUDE.md loads | AGENTS.md loads; disable project config to skip |
| Headless default | Skips protected resources | Hooks and .mcp.json load; allow rules do not | Loads |
Pi coding agent with a local model: llama.cpp and Ollama
This is Pi's strongest axis. Its llama.cpp page documents router mode: start llama serve without --model, -m or -hf, and the router discovers multiple GGUF models and loads them on demand. The block below is the doc's example with --jinja added, which the same page lists as the option that enables compatible chat templates and tool calling.
llama serve \ --no-models-autoload \ --jinja \ --host 127.0.0.1 \ --port 8080 \ -ngl all \ -c 32768
Then in Pi run /login llama.cpp (default URL http://127.0.0.1:8080, or set LLAMA_BASE_URL), /llama to load a model and /model to select it. --no-models-autoload keeps loading explicit, and --host 127.0.0.1 keeps the router local-only. Pi never deletes model files. On an air-gapped host, ignore the /llama download option, which searches Hugging Face, and stage GGUF files in the models directory.
For Ollama, LM Studio, vLLM and SGLang, Pi uses an OpenAI-compatible endpoint in models.json; its example points an ollama provider at http://localhost:11434/v1 with the openai-completions API.
OpenCode takes local models through an OpenAI-compatible provider; our OpenCode air-gapped setup guide walks through it against V1, whose config format V2 still loads. Claude Code is the odd one out: Anthropic's gateway doc says it does not support routing Claude Code to non-Claude models through any gateway. What breaks when you point Claude Code at a local model lists the failures, and the Codex CLI local model MCP post covers the same question for Codex.
Telemetry and offline mode in Pi, Claude Code and OpenCode
Pi's install report is a GET to pi.dev/api/report-install with the version, sent on a fresh install or a version change. enableInstallTelemetry (default true) controls it and the provider attribution headers; PI_TELEMETRY=0 overrides it. --offline disables automatic network activity, including catalog refreshes and version checks, and stops the install report. Use both, because attribution headers ride on requests to your own provider. This is the user-level lockdown file, ~/.pi/agent/settings.json:
{
"defaultProjectTrust": "never",
"enableInstallTelemetry": false,
"enableAnalytics": false,
"extensions": ["-builtin:mcp"]
}export PI_OFFLINE=1 export PI_TELEMETRY=0
"never" instead of the default "ask" means a developer cannot click through a trust prompt; a platform team grants trust per directory with /trust or --approve. A +builtin:mcp entry in project settings would override the user's -builtin:mcp, but project settings never load without trust, so the pair holds. Drop the extensions line once you have an approved mcp.json.
Claude Code's CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC disables metrics, error reports and feedback at once, but the docs say it does not affect the WebFetch domain safety check (skipWebFetchPreflight: true closes that) or official plugin marketplace auto-install. On Bedrock, Agent Platform and Foundry, metrics, error reports and /feedback default off. Transcripts sit in plaintext under ~/.claude/projects/ for 30 days by default.
OpenCode has no offline switch; PR #18235 proposing one closed unmerged on 2026-10-10. The paths close individually: OPENCODE_DISABLE_MODELS_FETCH, OPENCODE_DISABLE_FREE_MODELS=1, and the global update key set to disable.
| Pi v1.5.0 | Claude Code (Claude API) | OpenCode V2 (v2.2.5) | |
|---|---|---|---|
| Usage telemetry | Install/update report to pi.dev, default on | Metrics default on | None found in source |
| Other default traffic | Provider attribution headers; version check; model catalog refresh | Feedback command; quality surveys; WebFetch domain check | Model catalog from models.opencode.ai; free Zen models; update notify |
| Opt-in or conditional reporting | enableAnalytics, default off | Error reports on for some subscription sign-ins | OTLP export only if an endpoint is set |
| Single offline switch | --offline / PI_OFFLINE=1 | CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, with two exceptions | None |
Which coding agent fits on-prem and regulated teams
When the weights and the data sit inside your perimeter, the harness is the one component that touches both the repository and the network. That reframes the choice.
Pi and OpenCode both execute tool calls without asking by default, so for both the boundary is the container or VM: only the working folder mounted, egress limited to the inference server. Pi's containerization guide lists plain Docker, Docker Sandboxes, OpenShell and the Gondolin extension, and warns that mounting ~/.pi/agent exposes credentials, settings, extensions and sessions. If a VM is too heavy for every developer, an OS-level sandbox like nono already offers a Pi profile.
Between the two, Pi is easier to lock down for one workstation image (one settings file, two environment variables), and OpenCode's managed policies are the stronger control across a fleet. Claude Code wins only when the models are Claude; our Codex vs Claude Code comparison helps if Codex is also on your list, and the AI development tools hub collects the rest of our harness coverage.
| Team profile | Use | Why |
|---|---|---|
| Air-gapped, local open-weight models | Pi v1.5.0 in a container or VM | Trust gate fails closed, one offline switch, documented llama.cpp router |
| Org-wide control across many repos, local models | OpenCode V2 v2.2.5 in a container or VM, with managed policies | Policies override project config; no trust gate, so policy and isolation carry everything |
| Claude via Bedrock, Agent Platform or Foundry | Claude Code | Classifier or prompt before actions, OS sandbox, telemetry defaults off on those platforms |
| Mixed cloud team, several model vendors | Pi or OpenCode, isolated | Claude Code is unsupported on non-Claude models |
How to lock down Pi on one machine this week
Pick one developer machine. Build a container image with Pi v1.5.0 from your internal npm mirror, the four-key settings file above in ~/.pi/agent/settings.json, and PI_OFFLINE=1 and PI_TELEMETRY=0 in its environment. Mount only one repository, with egress allowed to your llama.cpp router and nothing else.
Then test the gate with a throwaway repository. Put a line in .pi/APPEND_SYSTEM.md asking the model to start every reply with a marker word, open Pi there, and confirm that no trust prompt appears and the marker never shows up. Move the same line into AGENTS.md and watch it take effect, which is the context-file hole, then confirm -nc closes it. If you run OpenCode instead, apply the permissions block in global config, set OPENCODE_DISABLE_PROJECT_CONFIG=1, and check that a project opencode.json has no effect.
FAQ
Quick answers to the questions this post tends to raise.



