# How to Track Stub Leaks in Cloudflare Computer Using `CAPNWEB_TRACK_STUBS` and `stubSnapshot()`

> Track stub leaks in Cloudflare Computer by setting CAPNWEB_TRACK_STUBS and using stubSnapshot() to get live reference counts and expose RPC layer leaks.

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

---

**Set the `CAPNWEB_TRACK_STUBS` environment variable to `1` or call `enableStubTracking()` to activate per-class stub counting, then invoke `stubSnapshot()` to retrieve a live map of reference counts that exposes leaks in your Cap'n Web RPC layer.**

Cloudflare Computer uses Cap'n Web RPC to expose objects across isolate boundaries, creating lightweight proxies called *stubs* that must be explicitly disposed to prevent memory leaks. The `cloudflare/computer` repository includes a zero-overhead diagnostic system—controlled by the `CAPNWEB_TRACK_STUBS` flag—that instruments every `RpcTarget` constructor and reports real-time statistics via the `stubSnapshot()` utility.

## Understanding Stub Lifecycle and Leak Detection

In the RPC layer, every remotely accessible object extends `RpcTarget`. When an instance is created, the base class invokes `trackStub(this)` defined in [`packages/rpc/src/debug.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/debug.ts). This function increments a counter stored in a `Map<string, number>` keyed by the constructor name, while a companion `WeakSet` guards against double-counting the same object. When the stub is disposed via `[Symbol.dispose]`, the proxy invokes `untrackStub()`, which decrements the counter and deletes the map entry when the count reaches zero. If a stub is never disposed, its counter remains positive, signaling a leak.

## Enabling Stub Tracking

The instrumentation is disabled by default to eliminate runtime overhead. You can activate it via an environment variable or programmatically.

### Environment Variable Configuration

At module load, [`packages/rpc/src/debug.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/debug.ts) evaluates `process.env.CAPNWEB_TRACK_STUBS` (Node.js) or `globalThis.CAPNWEB_TRACK_STUBS` (Workerd):

```typescript
// packages/rpc/src/debug.ts
const trackingEnabled = 
  process.env.CAPNWEB_TRACK_STUBS === "1" || 
  globalThis.CAPNWEB_TRACK_STUBS === true;

```

When this condition is truthy, `isStubTrackingEnabled()` returns `true` and the counting machinery activates.

### Programmatic Activation

For test harnesses where environment variables do not propagate, import `enableStubTracking()` and call it before running assertions:

```typescript
import { enableStubTracking } from "@cloudflare/computer-rpc/debug";

enableStubTracking(); // Forces tracking regardless of env var state

```

## Capturing Snapshots with `stubSnapshot()`

Once tracking is active, call `stubSnapshot()` to capture the current state of live stubs. The function returns a plain object mapping class names to active instance counts:

```typescript
import { stubSnapshot } from "@cloudflare/computer-rpc/debug";

const counts = stubSnapshot();
console.log(counts); // { MyService: 3, AnotherTarget: 0 }

```

If `isStubTrackingEnabled()` returns `false`, `stubSnapshot()` returns an empty object `{}` to ensure production code does not crash.

## Monitoring Leaks via HTTP Endpoint

The `computerd` CLI exposes these metrics via an HTTP endpoint. In [`packages/computerd/src/cli/computerd.ts`](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/cli/computerd.ts), the request handler imports `stubSnapshot` and returns its JSON serialization at `/debug/stubs`:

```typescript
// packages/computerd/src/cli/computerd.ts (approx. line 243)
body = request.method === "HEAD" 
  ? "" 
  : JSON.stringify(stubSnapshot());

```

You can inspect stub counts without modifying application code:

```bash
curl http://localhost:8787/debug/stubs

```

## Practical Implementation in Tests

The repository provides a reference implementation in [`packages/computer/tests/stub-soak.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/tests/stub-soak.test.ts). The test enables tracking, exercises RPC boundaries, and asserts that `stubSnapshot()` reports zero lingering stubs:

```typescript
import { enableStubTracking, stubSnapshot } from "@cloudflare/computer-rpc/debug";

// Enable tracking before RPC operations
enableStubTracking();

// ... run workloads that create and dispose stubs ...

const snap = stubSnapshot();
const allClean = Object.values(snap).every(v => v === 0);
expect(allClean).toBe(true);

```

The companion file [`packages/computer/tests/stub-soak-worker.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/tests/stub-soak-worker.ts) forwards the snapshot call across the isolate boundary, allowing the test harness to verify leak-free behavior in both the host and the worker.

## Summary

- **Activation**: Set `CAPNWEB_TRACK_STUBS=1` or call `enableStubTracking()` from [`packages/rpc/src/debug.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/debug.ts) to turn on counting.
- **Mechanism**: `RpcTarget` constructors invoke `trackStub()`, incrementing a per-class counter guarded by a `WeakSet`; disposal triggers `untrackStub()` to decrement it.
- **Inspection**: Use `stubSnapshot()` to retrieve live counts as a plain object, or query the `/debug/stubs` endpoint served by `computerd` in [`packages/computerd/src/cli/computerd.ts`](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/cli/computerd.ts).
- **Validation**: Assert that `stubSnapshot()` returns an empty object or zero values after test runs to confirm all stubs were properly disposed, following the pattern in [`packages/computer/tests/stub-soak.test.ts`](https://github.com/cloudflare/computer/blob/main/packages/computer/tests/stub-soak.test.ts).

## Frequently Asked Questions

### What triggers a stub leak in Cloudflare Computer?

A stub leak occurs when an `RpcTarget` instance is created but never disposed via its `[Symbol.dispose]` method. Because stubs maintain references to remote objects across isolate boundaries, failing to dispose them prevents garbage collection and wastes memory. The `trackStub()` function in [`packages/rpc/src/debug.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/debug.ts) increments a counter on construction, and `stubSnapshot()` reveals any counts that remain positive after the expected lifetime of the object.

### How do I enable stub tracking in production environments?

You should avoid enabling stub tracking in production because it consumes additional memory for the `Map` and `WeakSet` structures and CPU cycles for counting. If you must diagnose a leak in a live system, restart the isolate with `CAPNWEB_TRACK_STUBS=1`, capture snapshots via the `/debug/stubs` endpoint defined in [`packages/computerd/src/cli/computerd.ts`](https://github.com/cloudflare/computer/blob/main/packages/computerd/src/cli/computerd.ts), and then remove the flag once debugging is complete.

### What does `stubSnapshot()` return when tracking is disabled?

When `isStubTrackingEnabled()` returns `false` (the default state), `stubSnapshot()` immediately returns an empty object `{}`. This behavior ensures that production code checking for leaks does not throw or produce undefined results when the diagnostic flag is absent, as implemented in [`packages/rpc/src/debug.ts`](https://github.com/cloudflare/computer/blob/main/packages/rpc/src/debug.ts).

### Where is `trackStub` called in the RPC lifecycle?

`trackStub()` is invoked inside the `RpcTarget` base class constructor, which all RPC-exposed objects extend. This guarantees every stub is accounted for at the moment of creation. The corresponding `untrackStub()` call is triggered when the stub proxy's `[Symbol.dispose]` method is invoked, typically via explicit disposal or scoped usage with `using` declarations.