# Critical RPC Patterns Developers Need to Be Aware of in Cloudflare OS

> Master critical RPC patterns in Cloudflare OS. Learn stub lifecycle management, promise pipelining, and interface validation for efficient capability-based communication.

- Repository: [Cloudflare/cloudflare-os](https://github.com/cloudflare/cloudflare-os)
- Tags: deep-dive
- Published: 2026-09-04

---

**Cloudflare OS uses Cap’n Web RPC over persistent WebSockets to enable capability-based communication between front-end UI, back-end workers, and gatekeeper services, requiring developers to master stub lifecycle management, promise pipelining, and interface validation to prevent resource leaks and eliminate unnecessary network round-trips.**

Cloudflare OS is an open-source platform that orchestrates complex interactions between distributed workers and user interfaces through a unified RPC layer. Understanding the critical RPC patterns developers need to be aware of in Cloudflare OS is essential for building performant applications that avoid common pitfalls like memory leaks in Durable Objects or excessive latency. These patterns are implemented across the codebase, with core interface definitions located in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts) and supporting logic in the gatekeeper and backend packages.

## Capability Design Patterns

### Extend RpcTarget for Network-Visible Interfaces

Every public API in Cloudflare OS must be declared as an interface that extends `RpcTarget`. This design guarantees that methods are reachable over the network and can be used as first-class capabilities.

In [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts), the `PublicApi` interface follows this pattern at line 48, ensuring that all exposed methods can be invoked remotely. Similarly, `AuthenticatedApi` and gatekeeper interfaces adhere to this requirement, forming the foundation of the capability-based security model.

### Return RpcStub<T> for First-Class Capabilities

Methods that create new capabilities must return `RpcStub<T>` rather than concrete implementations. This allows the stub to be pipelined and passed around without awaiting a network round-trip, effectively treating remote references as local objects.

For example, `startGatekeeperLogin` returns `RpcStub<LoginAttempt>` (line 68 in [`api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/api.ts)), while `openGadget` returns `RpcStub<Overseer>` (line 78). These stubs represent live capabilities that can be invoked immediately or stored for later use.

## Performance Optimization Patterns

### Leverage Promise Pipelining for Zero-Round-Trip Calls

Cloudflare OS RPC calls accept `RpcStub` parameters directly, sending the stub "as-is" and resolving it on the server before execution. This enables "one-round-trip" interactions that dramatically reduce latency for chained operations.

The `openGadget` signature in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts) demonstrates this pattern by accepting a `configureObservers` argument of type `RpcStub<ObserverConfigCallback>`. When calling `openGadget`, you can pipeline subsequent method calls on the returned `RpcStub<Overseer>` without waiting for the initial call to complete, as shown in [`packages/workshop-backend/src/server.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-backend/src/server.ts).

## Resource Management Patterns

### Dispose Stubs to Prevent Durable Object Leaks

Every stub implements `[Symbol.dispose]()` and must be disposed when the capability is no longer needed. A dangling stub keeps a Durable Object alive on the server, causing resource leaks.

In the front-end, implement disposal in React effects:

```typescript
useEffect(() => {
  return () => {
    stub[Symbol.dispose]();
  };
}, [stub]);

```

This pattern is critical in [`packages/workshop-frontend/src/useWorkspaceOpen.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/useWorkspaceOpen.ts), where components must clean up `RpcStub<Overseer>` instances when unmounting to release server-side resources.

### Store Stubs Safely in React State

Never store a raw `RpcStub` directly in React state. Because a stub is a callable object, React’s state setter treats functions specially and may attempt to invoke the stub as a state updater, which throws an error.

Wrap the stub in an object before storing it:

```typescript
const [overseer, setOverseer] = useState<{ stub: RpcStub<Overseer> } | null>(null);

```

This pattern appears in [`packages/workshop-frontend/src/useWorkspaceOpen.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/useWorkspaceOpen.ts), preventing React from misinterpreting the callable stub as a functional state update.

## Communication Patterns

### Implement Observer-Configuration Flow

When a non-owner opens a gadget requiring additional gatekeeper accounts, the server calls `ObserverConfigCallback.configure` and expects an array of `ObserverAccountChoice`. This pattern allows the UI to prompt users just-in-time for missing credentials without extra round-trips.

The types `ObserverConfigCallback`, `ObserverBindingNeed`, and `ObserverAccountChoice` are defined in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts) (lines 78-94). The server invokes the callback stub passed to `openGadget`, and the client returns the selected accounts immediately, maintaining the pipelined flow.

### Use Subscription Pattern for Live Updates

Methods that set up push-style updates return a stub representing the subscription; disposing the stub cancels the subscription. This provides a clean, cancellable way to receive live updates for connected accounts or chat messages.

The `subscribeConnectedAccounts` method returns `RpcStub<{}>` (line 577 in [`api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/api.ts)). Keep this stub in a ref and dispose it when the component unmounts to cancel the server-side subscription and free associated resources.

## Validation and Security Patterns

### Annotate Implementations with @validateRpc()

Server-side classes that implement RPC interfaces should be decorated with `@validateRpc()`. This auto-generates runtime validation that matches TypeScript signatures, eliminating the need for hand-written input checks.

According to the project README and implementations in [`gatekeeper-google/src/linear.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/gatekeeper-google/src/linear.ts) (and similar gatekeeper files), this decorator ensures that method arguments conform to expected types before processing, preventing type confusion attacks.

### Validate Binding Names to Prevent Prototype Pollution

The helper `validateBindingName` enforces JavaScript identifier rules and prevents prototype-property collisions. This guarantees that binding maps used as plain objects cannot be poisoned by malicious names like `__proto__` or `constructor`.

Implemented in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts) (lines 207-220) and utilized in [`packages/workshop-shared/src/limits.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/limits.ts), this validation is essential when accepting user-defined binding names that will be used as object keys.

## Complete Implementation Example

Putting these patterns together creates a robust, leak-free interaction flow:

```typescript
// Start OAuth flow and pipeline the token retrieval
const { url, attempt } = await publicApi.startGatekeeperLogin('google');
window.open(url, '_blank');

// Wait for authentication without extra round-trips
const sessionToken = await attempt.wait();

// Open gadget with observer configuration callback
const overseer = await authenticatedApi.openGadget(
  'gadget-abc',
  undefined,
  {
    async configure(needs) {
      // Return selected accounts for missing gatekeepers
      return needs.map(n => ({
        gatekeeperId: n.gatekeeperId,
        accountId: chosenAccountId
      }));
    }
  } as RpcStub<ObserverConfigCallback>
);

// Immediately pipeline a call on the returned stub
await overseer.getChat();

// Subscribe to live account updates
const sub = await authenticatedApi.subscribeConnectedAccounts({
  add(id, desc, vendor, resources, valid) { /* handle add */ },
  remove(id) { /* handle remove */ },
  ready() { /* handle ready */ }
} as RpcStub<ConnectedAccountsSubscriber>);

// Clean up subscriptions and capabilities on unmount
useEffect(() => () => {
  sub[Symbol.dispose]();
  attempt[Symbol.dispose]();
}, []);

```

## Summary

- **Extend `RpcTarget`** on all RPC interfaces to ensure methods are network-reachable and capability-compatible.
- **Return `RpcStub<T>`** from factory methods to enable pipelining and first-class capability passing.
- **Dispose stubs explicitly** using `[Symbol.dispose]()` to prevent Durable Object leaks and resource exhaustion.
- **Wrap stubs in objects** before storing in React state to avoid React treating callable stubs as state updaters.
- **Pipeline method calls** on returned stubs to eliminate redundant network round-trips and reduce latency.
- **Use `@validateRpc()`** on server implementations to auto-generate runtime type validation.
- **Validate binding names** with `validateBindingName` to prevent prototype pollution attacks.
- **Implement subscription patterns** that return disposable stubs for clean, cancellable push updates.

## Frequently Asked Questions

### What is an RpcStub in Cloudflare OS and why must it be disposed?

An `RpcStub` is a callable proxy object representing a remote capability in Cloudflare OS. It maintains a reference to a server-side resource, often a Durable Object. You must dispose it using `[Symbol.dispose]()` to signal the server that the capability is no longer needed, preventing memory leaks and allowing the Durable Object to hibernate or shut down.

### How does promise pipelining reduce latency in Cloudflare OS RPC calls?

Promise pipelining allows you to send an `RpcStub` as a parameter to another RPC call without awaiting its resolution. The server resolves the stub internally before executing the method, enabling chained operations like `openGadget().getChat()` to complete in a single network round-trip rather than sequential requests. This pattern is implemented in [`packages/workshop-shared/src/api.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-shared/src/api.ts) for methods like `openGadget`.

### Why can't I store RpcStub objects directly in React useState?

React's `useState` hook treats function arguments specially, invoking them as state updater functions. Because an `RpcStub` is callable, storing it directly causes React to attempt execution as an updater, throwing an error. The solution, demonstrated in [`packages/workshop-frontend/src/useWorkspaceOpen.ts`](https://github.com/cloudflare/cloudflare-os/blob/main/packages/workshop-frontend/src/useWorkspaceOpen.ts), is to wrap the stub in a plain object like `{ stub: RpcStub<T> }` before storing it in state.

### What is the purpose of the @validateRpc() decorator?

The `@validateRpc()` decorator auto-generates runtime validation logic that matches the TypeScript signatures of your RPC interface methods. Applied to server-side implementations (e.g., in gatekeeper files), it removes the need for manual input validation while ensuring type safety and preventing malformed requests from reaching business logic.