Stop Using OpenCode
If you don’t know what OpenCode is, imagine a boot stamping on a<br>human face forever. The boot is made of TypeScript and the face is<br>everything we have learned about security and systems software since the<br>invention of the electronic computer in the 1940s. The creators describe it as an AI coding<br>agent. As far as I can tell it’s the most popular open-source coding<br>agent, and it currently has 161k stars on GitHub.
I’ve tried out OpenCode with a local LLM. My conclusion is that<br>OpenCode is clown-car turboslop with a security posture of “let me bend<br>over for you daddy”. Everyone using it should stop using it.
There are two parts to this post: annoying things<br>and alarming things . The second part is longer. I wrote<br>this post with reference to source code from OpenCode git version<br>baef5cd4.
I don’t consider anything in this post to be a security disclosure.<br>OpenCode is fundamentally a web-stack tool for piping<br>llm | bash, and all the issues I describe are in the “pipe”<br>part. The ways it fails are fascinating in the fractal nature<br>of the poor decision making, but the outcome was foregone.
I tried to keep discussion of LLM use separate from whether everyone<br>using LLMs should have their machines trivially exploited or<br>accidentally wiped. There is a post-script with some brief thoughts on<br>local LLMs.
Annoying Things
Let’s put security to one side for a moment and examine how OpenCode<br>fails as a tool even when it’s not causing you to get your shit<br>popped. There is a kind of Bethesda Effect with OpenCode where it’s<br>impossible to tell what is a bug and what is by design, so I stuck with<br>a description of “annoying”.
Prompt Cache Misses
Most local LLM servers use some variant of the OpenAI<br>/v1/chat/completions API. The idea is:
You POST a JSON blob with the entire conversation so<br>far.
You get back a stream of SSE events which add up to the<br>response.
The upload cost over a session is quadratic, and download is<br>amplified by wrapping tiny deltas in JSON with repeated metadata. Tool<br>calls use the elusive “double JSON encoding” so they can be serialised<br>as multiple JSON-encoded deltas that reassemble into more JSON.
The setup has one benefit, which is the server is stateless. As<br>usual, the way you make stateless things fast is:<br>state . The server caches evaluations. When it receives<br>a request, it:
Finds the longest matching cached prefix.
Evaluates from end of prefix to end of the last posted message<br>(“prefill”).
Generates new tokens until it encounters an end-of-sequence<br>token.
I used Qwen3.6-27B dense on an M4 Max, which has decent memory<br>bandwidth (~0.5 TB/s, high for a CPU SoC, low for a GPU). Token<br>generation is usable but it is extremely compute-bound in prefill. If my<br>server can’t find a good matching prefix for the request prompt when I’m<br>deep into the window then I might have to wait 10 minutes of max GPU<br>usage for it to start generating a response. That’s fine, because this<br>should happen rarely. Should.
Here are some of the ways OpenCode missed the memo on this one:
It globs your filesystem and re-reads AGENTS.md (injected in<br>turn-0 system prompt) on every SSE turn. If you put a quick note in<br>AGENTS.md to be read in the next session, you immediately force a full<br>re-evaluation.
It prunes context from tool calls on every agent → user transition, invalidating a large part<br>of the prefix.
Pruning just discards tool call results more than a fixed<br>distance const PRUNE_PROTECT = 40_000 behind the write<br>head. In the best case you’re taking a 40k context miss, which is<br>equivalent to reading a full-length novel per two or three<br>turns.
Agent → user transition includes<br>interruption, so if you need to pull the clanker out of a rabbit hole<br>and re-steer it, OpenCode immediately trashes the prompt cache and makes<br>you wait for a response.
Personal favourite: it puts the current date in<br>the turn-0 system prompt and re-evaluates every SSE turn. If you’re<br>using OpenCode at midnight you get a full prompt cache miss.
I think this tries to remediate the clanker tendency to believe the<br>current date is the date of its training cutoff, and refuse to believe<br>newer things exist. It’s still funny.
These are the prompt cache misses that fit solely in this category.<br>There are many more; I’ll call them out as we go.
Pruning
I mentioned pruning in the previous section. The prompt cache misses<br>aren’t worth it so I disabled it. The other glaring issue is the lack of<br>protection for early reads. It might not be obvious how completely<br>broken this is, so let’s work through an example. Suppose you start a<br>fresh session, and tell your clanker to first read a spec or<br>implementation plan, then write some code:
The spec is read into context.
The clanker goes and reads related code, very likely putting it<br>over the fixed 40k pruning threshold.
The clanker is ready to implement, but either immediately dives<br>down a dumb rabbit hole, or sits in chain-of-thought dithering about<br>something that is actually very simple...