OpenCode 2.0 Review: We Ran the Daemon-First CLI Against Its Own Criticism
Sources
- OpenCode 2.0 announcement (Anomaly)
- anomalyco/opencode on GitHub
- PR #54921: discover system configuration directory
- Issue #53074: /etc/opencode not respected in v2
- Issue #51107: V2 drops managed config on Windows
- HN: OpenCode 2.0 (773 points, 328 comments)
- HN: Major security regression merged into OpenCode V2
- OpenCode server documentation
- OpenCode config documentation
- OpenCode Enterprise documentation
- OpenCode Zen model gateway
OpenCode 2.0 is a client-server rewrite of the most-downloaded open-source coding agent — and the server now lives on your machine whether you asked for it or not. The 2.0 announcement post frames it as the natural maturation of the architecture; the 773-point HN thread treats it as a hostile act against process hygiene. Both positions have merit, and we tested both.
We downloaded the real @opencode/cli 2.0.29 npm launcher tarball (2,956 bytes — the binary ships separately), extracted the 211 MB Linux binary from its platform package, ran the daemon, drove sessions through the HTTP API, and read the source tree from the anomalyco/opencode repo (default branch dev, MIT-licensed, 213k stars). Every claim below is either a command we ran or a file we read. Where the criticism is wrong, we say so; where it is right, we show it.
Executive Scorecard
| Dimension | Score | Assessment |
|---|---|---|
| Reliability | 7/10 | Daemon restarts cleanly; sessions survive client crashes by design. But 2.0.x shipped 29 patch releases in 33 days (2.0.0 on 2026-09-11 to 2.0.29 on 2026-10-14), and the managed-config regression below proves the V1→V2 migration was not fully tested against enterprise setups. |
| DX | 8/10 | The API is genuinely good: clean OpenAPI 3.1 spec at /doc, typed clients, session persistence across clients. opencode service management verbs are sensible. But the 1.x→2.x breaking changes are real (plugins incompatible, server API replaced, tui.json moved) and the migration doc is a link, not a tool. |
| Cost | 6/10 | MIT license, no seat fees for OSS use. OpenCode Zen pay-as-you-go adds $20 balance with $1.23 card-processing fee, auto-top-up at $5, claims zero markup on model pricing. The hidden cost: a permanent ~210 MB RSS daemon per machine, plus 267 MB at startup in our measurement. |
| Security | 5/10 | The good: HTTP Basic Auth on the service API, localhost-only bind by default, enterprise managed-config tiers. The bad: the agent itself is explicitly not sandboxed (their own SECURITY.md), the daemon auto-spawns after being explicitly stopped, and the 2.0.x line dropped managed-config enforcement for 32 days — the exact mechanism enterprises use to lock fleets down. |
Architecture Mechanics: The Daemon-First Model
Version 1 had a client-server model that mostly sat unused. Version 2 inverts the relationship: the service is the product, and every client — TUI, web, desktop, your scripts — attaches to it. The announcement calls this the core of 2.0. Here is the actual topology, verified by probing the running daemon:
+--------------+ +-------------+ +--------------+
| TUI client | | Web client | | Desktop app |
+--------------+ +-------------+ +--------------+
| | |
+-----------------+------------------+
|
v
+--------------------------------------------------------+
| opencode service (daemon) |
| http://127.0.0.1:<random-port> |
| Basic auth: opencode:<password> |
| State: ~/.local/share/opencode/ |
| opencode.db (SQLite + WAL) |
| Auth: ~/.config/opencode/ |
| service.json (0600) |
+--------------------------------------------------------+
| | |
v v v
+--------------+ +--------------+ +--------------+
| provider | | MCP servers | | plugins |
| (Zen / BYOK) | | (Code Mode | | (80+ inter- |
| | | default) | | reloadable) |
+--------------+ +--------------+ +--------------+The daemon binds a random localhost port and writes its coordinates, plus a 43-character password, into ~/.config/opencode/service.json with 0600 permissions. Clients read that file and connect with HTTP Basic auth (opencode:<password>). Unauthenticated calls get a typed UnauthorizedError — we verified this empirically:
$ curl -s http://127.0.0.1:49374/api/info
{"_tag":"UnauthorizedError","message":"Authentication required"}
$ curl -s -u "opencode:$PASSWORD" http://127.0.0.1:49374/api/info
{"version":"2.0.29","pid":1689394,"urls":["http://127.0.0.1:49374"],
"paths":{"tmp":"/tmp/opencode"},"capabilities":{"persistentPty":true}}State persistence is SQLite with write-ahead logging (opencode.db + -shm + -wal files under ~/.local/share/opencode/). Sessions, messages, tool-call state, and token accounting all land in the database, which is why a crashed client loses nothing — the session lives in the daemon. This is a real architectural upgrade over 1.x's per-client state, and it is the single best design decision in 2.0.
Hands-On Breakdown: Driving the Real Daemon
We tested the three behaviors that matter to a platform team evaluating this for fleet deployment.
1. The daemon auto-spawns — even after an explicit stop
This is the HN thread's loudest criticism. Verdict: true. We stopped the service explicitly, confirmed stopped status, then ran a one-shot opencode run prompt. The service came back on its own:
$ opencode service stop
stopped
$ opencode service status
stopped
$ opencode run "Say exactly: SMOKE_OK"
> build · nemotron-3.5-super-vl-free
SMOKE_OK
$ opencode service status
http://127.0.0.1:49374Every CLI invocation — including one-shot run commands that exit immediately — leaves a persistent daemon behind. There is no documented config key to disable the auto-spawn behavior itself (the config reference covers the server's port, hostname, mDNS, and CORS, but not an autostart toggle; service set exposes no autostart key either — we checked). If you want the daemon gone, your options are opencode service stop after each use, or uninstalling. For CI runners and containers this is mostly irrelevant (the process dies with the container), but on a fleet of long-lived developer machines it means every developer who runs OpenCode once is running a local agent service indefinitely, holding ~210 MB RSS in our measurement, with shell-execution tooling attached to whatever directory the daemon was started in.
The counter-argument is fair too: the daemon gives you session resumption, multi-client attach (TUI + web + IDE simultaneously), and sub-200 ms reattach after a client crash. If your developers actually use those, the always-on service is a feature. The problem is that it is not opt-in, and the opt-out is a manual stop that the next command silently undoes.
2. The API is good — and quietly drifts from its own schema
The daemon publishes an OpenAPI 3.1 spec at /doc and a generated SDK is available. The repo's V2_HTTP_API_AUDIT.md documents 139 endpoints across ten groups — session lifecycle, credentials, MCP, web search, permissions, PTYs — with per-endpoint audit decisions. This is more API discipline than most coding agents have ever shown.
But the spec says additionalProperties: false on the session-create body, and the server does not enforce it:
# POST /api/session — spec: additionalProperties: false
$ curl -s -o resp.json -w "%{http_code}\n" -u "opencode:$PW" \
-X POST http://127.0.0.1:49374/api/session \
-H "content-type: application/json" \
-d '{"totally_bogus_field":"x","location":{"directory":"/tmp/oc-test"}}'
200
# The bogus field is silently dropped, the location is honored
$ jq -r '.data.location.directory' resp.json
/tmp/oc-testWe first hit this as a real footgun, not a synthetic probe: we created a session with a directory field at the top level (a plausible guess for anyone who has used 1.x), got a 200, and the session pinned itself to the daemon's own working directory — /home/pm — and wrote its first file there. The correct shape is location: { directory: ... }. A strict server would have rejected the malformed request with a 400; OpenCode accepted it and did something the caller did not ask for. In an agent harness that executes shell commands relative to its session directory, silent field-drop is a safety-relevant defect, not a style gripe. Test your client code against the daemon, not against the spec.
3. Code Mode: a real interpreter with real limits
Code Mode is 2.0's most interesting bet: the model writes JavaScript that orchestrates tools in a single round trip instead of issuing tool calls one at a time. It runs on @opencode/codemode, a minimal JS interpreter written in TypeScript — no Node.js VM, no embedded QuickJS (a decision HN commenters flagged as future regret, and we agree: the interpreter implements a subset of ES2023, and any language feature it lacks is now a model-facing runtime error). The interpreter enforces hard resource limits, verified in source (packages/codemode/src/interpreter/limits.ts):
/** Longest string a built-in or operator may produce. */
export const MAX_STRING_LENGTH = 1 << 24 // 16 MiB
/** Longest array a built-in may create or grow to. */
export const MAX_ARRAY_LENGTH = 10_000_000
/** Most promises that may be pending at once. */
export const MAX_PENDING_PROMISES = 10_000
/** Deepest nesting a value may have crossing to/from the host. */
export const MAX_VALUE_DEPTH = 32Executions also take per-run budgets — wall-clock timeoutMs, maxToolCalls, and output-byte caps, all optional with no defaults — from ExecutionLimits in packages/codemode/src/codemode.ts. All MCP servers are opted into Code Mode by default in 2.0, which is what lets the announcement claim "add a thousand [MCP servers] with minimal context bloat": the model gets a compact catalog of tool functions and writes code against them rather than paying per-tool schema tokens. The interpreter can also ingest OpenAPI endpoints as functions. The engineering is genuinely clever — it is the first mainstream harness to solve the MCP token-tax problem with code generation instead of context stuffing — but note the asymmetry: limits without defaults means an unspecified budget is unlimited, and the host application decides. OpenCode's own defaults, not the interpreter's, are the thing to audit.
The 32-Day Enterprise Lockdown Gap
The HN thread titled "Major security regression merged into OpenCode V2" (16 points, 2026-10-13) is misleadingly framed — the PR it links to is the fix, not the break. The real story is worse than either the thread title or the announcement suggests, and it is the finding that matters most to platform teams.
V1 loaded a managed config tier that outranks everything: /etc/opencode on Linux, /Library/Application Support/opencode on macOS, %ProgramData%\opencode on Windows. That is how an enterprise pins provider allowlists, disables /share, and enforces internal-gateway routing — the Enterprise docs are built around it, and the config precedence table lists managed settings as "highest priority, not user-overridable."
V2 shipped without it. Issue #53074 (filed 2026-10-04, by the same security engineer whose HN post later surfaced the fix) states it plainly: "V1 reads from /etc on linux, and treats this as immutable config users cannot override. We use this to prevent bad things. V2 does not appear to support this." Issue #51107 (2026-09-24) confirmed the same gap on Windows: "Organizations that use managed config to restrict providers lose that enforcement on upgrade to 2.x."
Read that again with fleet-deployment eyes. An organization that rolled out V2 between 2026-09-11 (2.0.0) and 2026-10-13 (when PR #54921 merged) had its lock tier silently disabled. Any user config could re-enable providers the managed tier had denied. The window was 32 days, closed in 2.0.29's lineage — but the fix's own PR description contains a detail that should make any security team pause: because ManagedPolicy.statements evaluates authored documents in reverse order, loading the system directory first "automatically gives its policies the highest file-based policy authority" through an entries.toReversed() implementation detail rather than an explicit precedence rule. The enforcement hierarchy of your fleet's agent policy rests on a list-reversal side effect. It works — we read the code in packages/core/src/config/discovery.ts — but it is the kind of implicit coupling that breaks silently in the next refactor.
The deeper lesson is procedural, not technical: the V2 rewrite dropped a documented, enterprise-critical enforcement tier and nobody noticed until a customer's rollout broke a month later. A project with 213k stars and claims of "millions of daily users" shipped a major version that broke its own documented security model, with no migration gate, no deprecation warning, no detection. That is the actual regression — and the HN poster's line holds: "It has shaken my trust that they understand security in the AI era or the needs of enterprise users."
The Model Tax Nobody Mentions
Our very first prompt — "What is 2+2? Answer in one word" — consumed 5,750 input tokens before producing three output tokens. The system prompt, tool schemas, and agent scaffolding are the floor; every conversation pays it on every turn that does not hit a cache. OpenCode's token accounting is honest about it (the session message records input 5750, output 3), and the free-tier model we tested (nemotron-3.5-super-vl-free via OpenCode Zen, the bundled curated-gateway provider) priced it at exactly $0.00. But scale that floor to a 40-hour coding week per developer and the harness overhead is a real line item: at 5,750 tokens per turn-start, a session that sends 100 prompts pays ~575k input tokens for scaffolding alone, before any of your code enters context. Against a harness with a leaner prompt (or aggressive prompt caching — the session recorded cache read: 0, write: 0 on our runs, so the free tier does not cache), that difference compounds. Measure it on your own workload before committing to a fleet license.
| Cost input | Verified value | Source |
|---|---|---|
| License | MIT, no fees for OSS use | repo LICENSE |
| Zen pay-as-you-go | $20 balance + $1.23 card fee; auto-top-up $20 at $5 remaining; zero-markup claim on model pricing | opencode.ai/zen |
| Free tier models | 10+ models incl. nemotron-3.5-super-vl-free, deepseek-v4.1-flash-free, step-5-preview-free — all tools-enabled, $0.00 recorded per session | /api/model on live daemon |
| Daemon memory | ~209–267 MB RSS (two measurements, minutes apart, idle) | ps on live daemon |
| Binary footprint | 211 MB unpacked per platform package; npm launcher tarball is a 2,956-byte stub that copies the binary via postinstall | registry tarballs, extracted |
| Prompt floor | 5,750 input tokens before the first output token | live session message record |
Critical Failure Modes
Distribution split-brain. The old opencode-ai npm package is frozen at 1.19.0; 2.x lives under @opencode/cli. GitHub releases still show v1.19.0 as latest (the 2.x line ships through npm and the desktop-app channel). Anyone following a stale install guide — or a package.json pin on opencode-ai — is silently running 1.x with no 2.0 features and, more importantly, with the managed-config gap already closed in 2.0.29 but not in their version. The HN thread caught this too; it is the single most common way to end up on the wrong side of the security fix.
Postinstall as a supply-chain surface. The npm package ships a stub binary that fails with a clear error if scripts are disabled, and the real 211 MB binary is copied from a platform-specific optionalDependency at install time. This is more defensible than a curl-pipe (it is still npm's integrity-checked registry, and the postinstall verifies the platform-package version matches the launcher exactly), but it means the code you run is two layers removed from what npm audit sees, and organizations that mandate --ignore-scripts (a sane policy) will get a broken install with a manual remediation step. Fleet tooling needs to account for the platform-package pattern.
No sandbox, by design. Their own SECURITY.md, verbatim: "OpenCode does not sandbox the agent. The permission system exists as a UX feature... it is not designed to provide security isolation." The tools include shell execution, file write, and web access. The 2.0 architecture makes this more consequential, not less: a persistent daemon holding a session that can run shell commands in your project directory, restartable by any future OpenCode invocation, with PTY persistence (capabilities.persistentPty: true in our probe). If you would not hand a junior engineer an always-on root shell in your repo, do not hand an unsandboxed agent daemon one either. Run it in a container or VM — their security policy says the same.
SECURITY.md's AI-reports ban. "We do not accept AI generated security reports... If you submit one that will be an automatic ban from the project." For a tool whose users are, definitionally, people running AI agents, this is a striking policy — and a practical problem: modern vuln discovery is increasingly automated, and a project that bans its own user base's tooling from its security process is choosing blind spots. We are not aware of a real incident caused by this policy; we are saying it is a governance red flag for any organization whose security program includes automated scanning.
Compaction boundary. The V2 source shows active work on context compaction (a 2026-10-15 commit: "fix(core): fit compaction output limit to the request actually sent") — evidence both that long sessions hit context limits and that the compaction path is recent enough to still be receiving correctness fixes. Treat very long sessions as a moving target and pin your fleet's version.
Who Should Skip This
| If you are… | …then |
|---|---|
| An enterprise security team that needs fleet-wide agent policy enforcement | Wait for the managed-config tier to prove stable post-#54921 (merged 2026-10-13; as of 2.0.29 it is days old). Verify the fix on a canary group before rollout, and do not trust the docs' precedence table alone — test it. The 32-day gap is a warning about process, not just code. |
| A team running agents in CI/CD or containerized one-shot workflows | Mostly fine — the daemon dies with the container. But pin @opencode/cli by exact version; the 29-patches-in-33-days cadence means latest is a moving target, and the opencode-ai → @opencode/cli package split means your lockfile may lie about what you run. |
| A developer who wants an agent that does not outlive the terminal you closed | Skip 2.0 or resign yourself to opencode service stop after each session — and know the next command will respawn it. There is no autostart off-switch in the documented config surface. |
An organization that mandates --ignore-scripts | Expect a broken first install (stub binary, clear error message, manual platform-package step). Not a blocker, but budget for the tooling exception. |
| Anyone evaluating "open source Claude Code" on architecture alone | Read the SECURITY.md before the README. The client-server split, OpenAPI surface, and plugin hot-reload are genuinely strong; "not a sandbox" + auto-spawning daemon + policy-on-a-list-reversal are the trade. If your threat model includes prompt injection reaching a shell tool, none of 2.0's architecture changes that. |
Verdict
OpenCode 2.0 is the most architecturally serious open-source coding agent available today — a real client-server system with a typed 139-endpoint API, SQLite-backed session persistence, hot-reloadable plugins, and a Code Mode interpreter that actually solves MCP's token tax. It is also a project that shipped a major rewrite that silently disabled its enterprise enforcement tier for a month, cannot decide which npm package it lives in, respawns its daemon after you kill it, and whose own security policy tells you it is not a sandbox while its daemon model gives an unsandboxed agent a permanent foothold on your machine.
For individual developers on non-adversarial code: excellent tool, real value, hard to beat at MIT-plus-free-tier. For platform teams: the architecture is exactly what you would design if you were building an agent fleet today — and the operational discipline is not yet at the level the architecture implies. Pin versions, test the managed-config tier yourself, containerize the daemon, and treat every "2.x is stable now" claim as provisional until the patch cadence slows from twenty-nine releases a month to something a change review board can follow.
All measurements taken 2026-10-15 against @opencode/cli 2.0.29 on Linux x64: live daemon probes via the service HTTP API, source inspection from the dev branch tarball, npm registry metadata, and the referenced GitHub issues and PR. The free-tier model (nemotron-3.5-super-vl-free) answered all test prompts at $0.00 recorded cost.