# WorkspaceStub vs WorkspaceRuntimeStub vs WorkspaceFilesystemStub in Cloudflare Computer: Functional Differences Explained

> Understand the functional differences between WorkspaceStub WorkspaceRuntimeStub and WorkspaceFilesystemStub in Cloudflare Computer Explore how each stub handles filesystem and runtime APIs across the Workers RPC boundary

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: deep-dive
- Published: 2026-08-07

---

**Cloudflare Computer splits the remote view of a Workspace into three specialized stub objects: WorkspaceStub serves as the entry point container, while WorkspaceFilesystemStub and WorkspaceRuntimeStub each proxy a specific slice of the filesystem or runtime API across the Workers RPC boundary.**

The `@cloudflare/computer` package implements a clean separation of concerns for remote workspace access. When a Worker or Durable Object needs to interact with a workspace hosted in another Durable Object, it doesn't receive the full `Workspace` instance directly. Instead, it receives type-safe stub objects that forward method calls across the RPC layer. Understanding the functional differences between these stubs is essential for building correct distributed applications on the platform.

## WorkspaceStub: The Top-Level Facade

`WorkspaceStub` acts as the **entry point and container** for all remote workspace operations. It holds a reference to the host-side `Workspace` instance and exposes specialized sub-stubs as properties.

The class is defined in **[`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts)** with a constructor that stores the `Workspace` reference and registers the stub for leak tracking. You obtain a `WorkspaceStub` by calling `Workspace.stub()` — see the implementation in **[`packages/computer/src/workspace.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/workspace.ts)** at line 519.

```typescript
// Acquire from a Durable Object that owns the workspace
const ws: WorkspaceStub = await env.WSD.get(id).getWorkspace();

// Access sub-stubs through properties
const fs: WorkspaceFilesystemStub = ws.fs;
const runtime: WorkspaceRuntimeStub = ws.runtime;
// Also available: ws.assets, ws.artifacts

```

Key responsibilities:

- Provides a **single entry point** for workers to obtain any workspace capability
- Exposes sub-stubs as lazy-loaded properties
- Handles top-level lifecycle tracking via `trackStub(this)` / `untrackStub(this)` (lines 96-101)

## WorkspaceFilesystemStub: Filesystem Operations

`WorkspaceFilesystemStub` is a **dedicated RPC proxy for the `Workspace.fs` API**. It mirrors every public method of the in-process filesystem interface while preserving JSRPC-compatible types.

The implementation begins at **line 90 in [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts)**. Each method follows a consistent pattern: wrap the underlying call in `withSpan` for observability, then forward arguments unchanged.

Available methods include:

- `readFile(path, encoding?)` — returns `Uint8Array` or string
- `writeFile(path, data)` — accepts `string`, `Uint8Array`, or `ReadableStream`
- `stat(path)`, `mkdir(path, options)`, `rm(path, options)`

```typescript
// Write binary data
await ws.fs.writeFile('/data.bin', new Uint8Array([0x00, 0x01, 0x02]));

// Read as UTF-8 string
const text = await ws.fs.readFile('/config.json', 'utf8');

// Stream large files
const stream = await ws.fs.createReadStream('/large.log');

```

The stub ensures **type preservation across the RPC boundary**: `Uint8Array` and `ReadableStream` objects serialize correctly through the Workers RPC layer. All operations are traced via `withSpan` for debugging.

## WorkspaceRuntimeStub: Process Execution

`WorkspaceRuntimeStub` proxies **runtime command execution** through the `Workspace.runtime` API. Unlike the filesystem stub's many small methods, this stub focuses on a single high-impact operation: spawning processes.

The class definition starts at **line 74 in [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts)**. Its core method is:

```typescript
exec(command: string, options?: ExecOptions): Promise<WorkspaceRuntimeExecHandleStub>

```

The returned `WorkspaceRuntimeExecHandleStub` (lines 80-140) provides three critical capabilities:

| Method | Purpose |
|--------|---------|
| `result()` | Await completion; returns `{ exitCode, stdout, stderr }` |
| `stream()` | Access live stdout/stderr as a `ReadableStream<Uint8Array>` |
| `kill()` | Terminate the running process |

```typescript
// Start a command
const handle = await ws.runtime.exec('npm install');

// Option 1: Wait for completion
const { exitCode, stdout } = await handle.result();
if (exitCode !== 0) {
  throw new Error(`npm failed: ${stdout}`);
}

// Option 2: Stream output for real-time logs
const stream = handle.stream();
const reader = stream.getReader();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  process.stderr.write(new TextDecoder().decode(value));
}

// Option 3: Timeout-based cleanup
setTimeout(() => handle.kill(), 30000);

```

The `exec()` implementation first ensures the workspace is ready, then forwards to `this.#ws.runtime.exec` with full tracing.

## Key Functional Differences

| Aspect | WorkspaceStub | WorkspaceFilesystemStub | WorkspaceRuntimeStub |
|--------|-------------|------------------------|----------------------|
| **Primary role** | Container/entry point | Filesystem proxy | Process execution proxy |
| **Number of methods** | Few properties (fs, runtime, assets, artifacts) | Many methods (read, write, stat, mkdir, rm, etc.) | Few methods (exec, plus handle methods) |
| **Return values** | Sub-stub instances | Primitive types, buffers, streams | Exec handle stub with streaming interface |
| **Streaming support** | Indirect (via sub-stubs) | File streams | Live process stdout/stderr |
| **Lifecycle disposal** | Tracks own reference | Disposes filesystem proxy only | Disposes exec handle when finished |

## How Stubs Relate: A Complete Example

```typescript
import { WorkspaceStub } from '@cloudflare/computer';

// 1. Obtain the top-level stub
const ws: WorkspaceStub = await env.WORKSPACE_DO.get(id).getWorkspace();

// 2. Filesystem operations through WorkspaceFilesystemStub
await ws.fs.mkdir('/project', { recursive: true });
await ws.fs.writeFile('/project/package.json', JSON.stringify({
  name: "my-project",
  scripts: { build: "tsc" }
}));

// 3. Runtime operations through WorkspaceRuntimeStub
const build = await ws.runtime.exec('npm run build', {
  cwd: '/project',
  env: { NODE_ENV: 'production' }
});

// 4. Stream build output in real-time
const reader = build.stream().getReader();
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  console.log(new TextDecoder().decode(value));
}

// 5. Get final result
const { exitCode, stdout } = await build.result();
if (exitCode !== 0) {
  console.error('Build failed:', stdout);
}

```

## Summary

- **WorkspaceStub** is the **root container** that workers receive when accessing a remote workspace; it exposes `fs` and `runtime` properties but implements no capability logic itself.

- **WorkspaceFilesystemStub** provides **fine-grained filesystem access** with methods like `readFile`, `writeFile`, and `stat`; it preserves binary types across RPC.

- **WorkspaceRuntimeStub** enables **process execution** with streaming output support; its `exec()` method returns a handle for async result retrieval, live streaming, or process termination.

All three stubs participate in **leak tracking** through `trackStub`/`untrackStub` calls and use `withSpan` wrappers for distributed tracing. The stub architecture lets Cloudflare Computer maintain type safety across the Workers ↔ Durable Object boundary while keeping each RPC surface focused and minimal.

## Frequently Asked Questions

### Can I use WorkspaceFilesystemStub or WorkspaceRuntimeStub directly without WorkspaceStub?

No — these stubs are only accessible as properties of a `WorkspaceStub`. The `fs` and `runtime` properties are instantiated lazily when accessed. See the property definitions in [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts) lines 74-90.

### What happens if I don't call `kill()` on a runtime exec handle?

The process continues running until it completes naturally. However, all stubs track their lifecycle through `trackStub`, and unreferenced stubs are eventually garbage collected. For explicit cleanup, call `handle.kill()` or let the handle fall out of scope.

### Are streaming methods like `stream()` backpressure-safe?

Yes — the `ReadableStream` returned by `handle.stream()` is a standard Web Streams implementation that respects backpressure. The underlying RPC layer in Workers handles flow control automatically.

### How do I know which encoding to use with `readFile()`?

Omit the encoding parameter to receive `Uint8Array`, or specify `'utf8'` for strings. This matches Node.js `fs` conventions. Binary files should use the default; text files typically use `'utf8'`.