denoland/celldPublic

self-hosted, distributed Durable Objects

Expand nodejs_compat builtins (node:os, node:fs, node:module, child_process)

TA

## Context

I attempted to run **OpenCode** (`@opencode-ai/server`) inside Celld as a stateful coding agent running in a Durable Object with SQLite persistence:

```typescript import { DurableObject } from "cloudflare:workers"; import { OpenCodeWorkerd } from "@opencode-ai/server";

export class OpenCodeDO extends DurableObject { async fetch(req: Request) { // 1. Initialize OpenCode engine const opencode = await OpenCodeWorkerd.create({ storage: this.ctx.storage, config: { defaultModel: "google/gemini-2.5-flash" }, });

// 2. Create session & run prompt const session = await opencode.session.create(); await opencode.session.prompt({ sessionID: session.id, text: "hello world", });

return new Response("OK"); } } ```

When building and deploying this on Celld (v0.3.0), the code fails across four sequential phases due to Node compatibility gaps.

## Compatiblity Gaps ### Missing CJS require

``` ReferenceError: require is not defined at node_modules/@opencode-ai/server/... ```

### `node:os`

``` TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of NodeStub at path.join (node:path) ```

### `node:fs`

``` FiberFailure: (FiberSet) Defect: TypeError: stats.isDirectory is not a function at SessionRunner.resolveWorkspaceDirectory ```

### `node:child_process`

``` TypeError: Cannot read properties of undefined (reading 'on') at Shell.create (node_modules/@opencode-ai/server/...) ```

## Minimal, reproducible example

```js // repro-worker.js import os from "node:os"; import path from "node:path"; import fs from "node:fs"; import { spawn } from "node:child_process";

export default { async fetch(req) { const results = [];

// 1. node:os string return contract try { const home = os.homedir(); // Expectation: home is a string (e.g. "/root") // Reality in Celld: home is a NodeStub Proxy object path.join(home, ".config"); results.push("✓ os.homedir() contract: OK"); } catch (err) { results.push(`✗ os.homedir() contract failed: ${err.message}`); }

// 2. node:fs stat / realpathSync interface try { const real = fs.realpathSync("/workspace"); if (typeof real !== "string") { throw new TypeError(`Expected string, got ${typeof real}`); } fs.stat(real, (err, stats) => { if (!stats || typeof stats.isDirectory !== "function") { throw new TypeError("stats.isDirectory is not a function"); } }); results.push("✓ node:fs stat/realpath contract: OK"); } catch (err) { results.push(`✗ node:fs contract failed: ${err.message}`); }

// 3. node:module / dynamic CJS require try { if (typeof require === "undefined") { throw new ReferenceError("require is not defined"); } results.push("✓ require exists: OK"); } catch (err) { results.push(`✗ require failed: ${err.message}`); }

// 4. node:child_process spawn interface try { const proc = spawn("bash", ["-c", "echo 1"]); if (!proc || !proc.stdout || typeof proc.stdout.on !== "function") { throw new TypeError("proc.stdout.on is not a function"); } results.push("✓ child_process.spawn contract: OK"); } catch (err) { results.push(`✗ child_process.spawn failed: ${err.message}`); }

return new Response(results.join("\n"), { headers: { "content-type": "text/plain" }, }); }, }; ```

  1. bartlomiejuassigned bartlomieju2 months ago on GitHub

  2. ctrochalakissubscribed2 months ago on GitHub

  3. YE

    Can you confirm whether you would accept a patch to enable these missing `nodejs_compat` built-ins?

    I have a proof of concept that gets Celld closer to the API surface available in Durable Objects/workerd:

    - `node:os` returns the same privacy-preserving values as workerd instead of exposing host details. - `node:fs` uses a filesystem scoped to the isolate, with ephemeral `/tmp` and read-only `/bundle`, without exposing the host filesystem. - Sync, callback, and promise APIs share the same implementation, while unsupported operations fail explicitly instead of returning the generic stub.

    The first patch would cover `node:os` and the filesystem APIs needed by OpenCode. I can treat `node:module` and `node:child_process` separately since they have different security and runtime implications.

    Is this an approach you would be open to reviewing?

  4. BA

    This is mostly already implemented and will be released in the next version.

  5. BA

    I'm gonna close this one as we implemented necessary APIs for OpenCode and Pi

  6. bartlomiejuclosed2 months ago on GitHub

  7. bartlomiejureferenced fd7b96b993f52 months ago on GitHub

  8. bartlomiejureferenced b61356b29deb2 months ago on GitHub

  9. bartlomiejureferenced 7ac68e0bd2a72 months ago on GitHub

  10. bartlomiejureferenced 10cb1303dac72 months ago on GitHub