# How to Debug Stub Accumulation Using the Debug Surface and `GET /__computerd/stubs`

> Debug stub accumulation in Cloudflare Computer with GET /__computerd/stubs and COMPUTER_DEBUG=1. Learn to dispose of stubs using client.dispose() to prevent memory leaks.

- Repository: [Cloudflare/computer](https://github.com/cloudflare/computer)
- Tags: how-to-guide
- Published: 2026-08-16

---

**Enable `COMPUTER_DEBUG=1` and query `GET /__computerd/stubs` to expose live RpcTarget instance counts, then dispose of parent stubs via `client.dispose()` to prevent memory leaks in long-running Computer sessions.**

The `cloudflare/computer` platform uses **Cap'n Proto-based RPC stubs** to represent remote capabilities like `fs`, `shell`, and `git` that live inside the Durable Object (`computerd`). Because the Cap'n Proto RPC layer does **not** automatically garbage-collect remote stubs, unused stub objects accumulate over time and cause memory pressure or "stub-leak" failures. This article shows how to use the built-in debug surface to diagnose stub accumulation and fix the root cause.

## What Is Stub Accumulation?

Stub accumulation occurs when your code creates `RpcTarget` stub instances without properly disposing of them. In [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts), the `Stub` class wraps remote capabilities as accessor properties (lines 555-618). Each stub maintains a reference to sub-stubs like `fs` or `shell`, and these references persist until explicitly released.

The disposal contract is documented in [`docs/11_lifecycle.md`](https://github.com/cloudflare/computer/blob/main/docs/11_lifecycle.md): callers must invoke `dispose()` on parent stubs to cascade cleanup to all child stubs. Failure to do so leaves `RpcTarget` instances tracked by the runtime, consuming memory indefinitely.

## Enabling the Debug Surface

The `computerd` CLI exposes stub diagnostics when debug mode is active. You can enable this two ways:

- **CLI flag:** `--debug`
- **Environment variable:** `COMPUTER_DEBUG=1`

When enabled, the server maintains an internal registry of all live `RpcTarget` instances by subclass name. This registry powers the HTTP debug endpoint without impacting production performance when disabled.

## Querying Stub Counts with `GET /__computerd/stubs`

The debug surface exposes a JSON endpoint at `GET /__computerd/stubs`. The handler is implemented in [`packages/computerd/src/cli/computerd.ts`](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/cli/computerd.ts) (line 236).

The endpoint returns a map of stub class names to live instance counts:

```json
{
  "fs": 12,
  "shell": 5,
  "git": 1,
  "runtime": 3
}

```

Each key represents an `RpcTarget` subclass, and the value indicates how many instances currently exist. A steadily increasing count for any stub type signals a leak in your disposal logic.

### Example: Fetching Stub Counts from a Test

```typescript
import { exec } from "child_process";
import fetch from "node-fetch";

process.env.COMPUTER_DEBUG = "1";
exec("npm run start:computerd &", (err) => {
  if (err) throw err;
});

(async () => {
  // Wait for server ready (use proper health checks in production)
  await new Promise((r) => setTimeout(r, 2000));

  const res = await fetch("http://127.0.0.1:8787/__computerd/stubs");
  const stubs = await res.json();
  console.log("Live stub counts:", stubs);
  // Expected for clean state: { fs: 0, shell: 0, git: 0, ... }
})();

```

## Interpreting Stub Accumulation Patterns

Different accumulation patterns point to different root causes:

| Pattern | Likely Cause | Fix |
|---------|-----------|-----|
| `fs` steadily climbing | File system stubs created per request without disposal | Scope `fs` usage and add `dispose()` |
| `shell` sporadic spikes | Shell commands executed without cleanup | Wrap in `try/finally` with disposal |
| Multiple stubs rising together | Parent stub never disposed | Ensure `client.dispose()` called |
| Count never returns to zero | Stub held in closure or global | Audit references and weak maps |

Query `GET /__computerd/stubs` before and after test scenarios to isolate which code paths leak stubs.

## Proper Stub Disposal to Prevent Accumulation

The [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts) file defines disposal semantics where parent stub cleanup cascades to all sub-stubs. Always call `dispose()` on the root `Computer` client when work completes:

```typescript
import { Computer } from "@cloudflare/computer";

async function runTask() {
  const client = new Computer({ /* connection config */ });
  const { fs, shell } = await client.fsShell(); // stub bundle

  try {
    await fs.writeFile("/tmp/foo.txt", "data");
    await shell.exec("echo hello");
    return { success: true };
  } finally {
    // Critical: dispose parent to release all sub-stubs
    await client.dispose();
  }
}

runTask().catch(console.error);

```

Without the `finally` block, exceptions would skip disposal and leak stubs. Use this pattern universally for stub-bearing operations.

## Validating Fixes with Stub-Soak Testing

[`packages/computer/tests/stub-soak.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/tests/stub-soak.test.ts) provides a reference implementation for stress-testing stub lifecycles. The test repeatedly creates and disposes stub bundles, verifying that `GET /__computerd/stubs` returns zero counts after each iteration.

Run this test while monitoring the debug endpoint to confirm your disposal logic works under load. The pattern demonstrates:

1. Create stub instances
2. Perform operations
3. Call `dispose()`
4. Assert all stub counts return to zero via `GET /__computerd/stubs`

## Automating Stub Leak Detection in CI

Add a health-check step to your integration pipeline that fails builds when stub counts exceed thresholds:

```typescript
// ci/stub-check.ts
async function assertNoStubLeaks(threshold = 5) {
  const res = await fetch("http://localhost:8787/__computerd/stubs");
  const stubs = await res.json();

  const offenders = Object.entries(stubs).filter(([, count]) => count > threshold);
  if (offenders.length > 0) {
    throw new Error(`Stub leak detected: ${JSON.stringify(offenders)}`);
  }
}

```

Execute this after integration tests to catch regressions before deployment.

## Summary

- **Enable debugging** with `COMPUTER_DEBUG=1` to activate stub tracking in `computerd`.
- **Query `GET /__computerd/stubs`** to retrieve live `RpcTarget` counts per stub type.
- **Interpret growth patterns** to identify which code paths leak specific stub classes.
- **Dispose parent stubs** via `client.dispose()` to cascade cleanup to all sub-stubs.
- **Validate with soak tests** from [`packages/computer/tests/stub-soak.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/tests/stub-soak.test.ts).
- **Automate in CI** to prevent stub leak regressions.

## Frequently Asked Questions

### What causes stub accumulation in `cloudflare/computer`?

Stub accumulation occurs when Cap'n Proto RPC stubs are created without explicit disposal. The RPC layer does not garbage-collect remote capabilities automatically, so each `fs`, `shell`, or `git` stub persists in memory until `dispose()` is called on its parent, as implemented in [`packages/computer/src/stub.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/src/stub.ts).

### How do I interpret the `GET /__computerd/stubs` response?

The JSON response maps stub class names to live instance counts. In [`packages/computerd/src/cli/computerd.ts`](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/cli/computerd.ts) (line 236), the endpoint aggregates `RpcTarget` instances by their constructor name. Non-zero values are normal during active work, but counts that grow endlessly or never return to baseline indicate disposal failures.

### Why must I dispose stubs explicitly instead of relying on garbage collection?

Cap'n Proto maintains strong references to exported capabilities for RPC correctness. The [`docs/11_lifecycle.md`](https://github.com/cloudflare/computer/blob/main/docs/11_lifecycle.md) disposal contract explains that JavaScript garbage collection cannot reach across this boundary—only explicit `dispose()` signals the runtime to release the underlying capability, preventing memory exhaustion in long-running Durable Objects.

### Can I use the debug surface in production?

The debug surface adds minimal overhead when enabled, but `COMPUTER_DEBUG=1` is designed for development and testing. Production deployments should disable debug mode and rely on CI automation with `GET /__computerd/stubs` to validate stub hygiene before release, as shown in [`packages/computer/tests/stub-soak.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/tests/stub-soak.test.ts).