How OpenCode Runs Code Mode
Recently I stumbled upon a short tweet that hinted at how OpenCode does Code Mode without a sandbox, in the host process. This triggered my curiosity and I wanted to learn how, so I did some quick research and found this document.
The trick is that the code is interpreted rather than run as normal JavaScript.
First the TypeScript bits get stripped away and Acorn parses the code into an AST. A custom interpreter then walks the AST and executes each node itself. It only supports a small subset of JavaScript, so things like eval, imports or classes just return an error. The code itself is never run directly by eval or the JS engine.
And this all happens in the host process with no OS-level sandbox around it. The code can only do what the interpreter supports and call the tools it's given.
The Tool Boundary
The only way for the code to reach the outside world is through tools that OpenCode hands it. Something like:
const order = await tools.orders.lookup({ id: "order_42" });
return { status: order.status };
The code can call tools, mix their results and return only the bits it needs. Anything outside, like the filesystem, processes, env variables, network or the app itself, has to go through a tool. Whatever goes in and out of a tool has to be plain data, basically JSON.
In OpenCode each run gets the MCP tools that your current permissions allow. Built-in tools like bash aren't available in there. Every tool call also goes through OpenCode's permission check before it runs. So the interpreter limits what the code can touch and OpenCode still decides which tools it gets to call.
A Small Language
The language is small but it's enough to orchestrate tools: variables, conditions, loops, functions, common data operations and promises. You can run calls one after the other or in parallel:
const [user, issues] = await Promise.all([
tools.github.get_user({ name: "octocat" }),
tools.github.list_issues({ owner: "octocat", repo: "hello-world" }),
]);
return issues.map((issue) => ({ title: issue.title, author: user.name }));
What Could Go Wrong?
The tools can still do real damage. Some MCP servers are remote but others run on your machine. A tool can read files, change data or run commands if that's what it was built for. The interpreter doesn't make a powerful tool safe, so permissions and checks inside each tool still matter.
The interpreter itself can go wrong too. A bug in it could break the boundary. Heavy code also runs inside OpenCode itself. An infinite loop can be cancelled but something like a bad regex can freeze OpenCode until it finishes, and there's no timeout by default.
I find this an interesting approach. It avoids spinning up a full sandbox for something that's mostly about coordinating tools. The tradeoff is that the interpreter and every tool it exposes become part of the security boundary.