How to Debug Runtime Type Issues in Cloudflare Computer: A Step-by-Step Guide
Enable RPC debug logging with enableRpcDebug(), validate stub shapes using assertRpcImplements(), and verify execution-runtime mappings with assertExecutionRuntime() to catch type mismatches before they propagate.
Cloudflare Computer separates the workspace (the in-process file system) from the runtime (the sandbox that executes code). This architecture relies on strongly-typed RPC boundaries that can fail silently when types diverge across process boundaries. Understanding how to debug runtime type issues in Cloudflare Computer requires knowledge of three interconnected layers: the Cap'n Proto wire contract, the runtime implementation, and the type assertion utilities that catch mismatches early.
Understanding the Runtime Type Architecture
Types flow across three main layers in the Cloudflare Computer stack. When a runtime type error surfaces, the root cause typically lives in one of these boundaries.
| Layer | What It Provides | Key Source Files |
|---|---|---|
| Workspace ↔ Runtime RPC | Strongly-typed Cap'n Proto interface for exec, pull, push, etc. |
[runtime/wire.ts](https://github.com/cloudflare/computer/blob/main/packages/computer/src/runtime/wire.ts) |
| Runtime Implementation | Concrete classes driving containers or workers, exposing WorkspaceRuntime API |
[runtime/runtime.ts](https://github.com/cloudflare/computer/blob/main/packages/computer/src/runtime/runtime.ts) |
| Type Helpers & Assertions | Utility types and runtime-only checks like assertExecutionRuntime |
[runtime/types.ts](https://github.com/cloudflare/computer/blob/main/packages/computer/src/runtime/types.ts) |
Common sources of runtime type failures include:
- Mismatched RPC contracts — The Cap'n Proto definition in
wire.tsdiverges from TypeScript declarations - Incorrect stub casting — The
RpcStubproxy inpackages/rpc/src/client.tscasts to nominal types without runtime validation - Runtime-only globals — Missing or differently-shaped globals like
processorWebSocketin worker environments - Exec-tracking mismatches — Stale execution ID mappings between
WorkspaceRuntimeinstances
Step-by-Step Debugging Workflow
Reproduce the Error in a Test
Write a minimal reproduction in packages/computer/tests/runtime.test.ts. The test harness spins up a real container (computerd) or worker stub, giving you authentic cross-process behavior.
// packages/computer/tests/runtime.test.ts
import { describe, it, expect } from "vitest";
import { createTestWorkspace } from "./helpers";
describe("runtime type issues", () => {
it("should reproduce the type mismatch", async () => {
const workspace = await createTestWorkspace();
// Your reproduction case here
const result = await workspace.runtime.exec({ /* ... */ });
// Assertion that fails due to type mismatch
});
});
Enable Detailed RPC Logging
Import the debug helper from the RPC package to surface exact payloads:
import { enableRpcDebug } from "@cloudflare/rpc/debug";
enableRpcDebug(); // prints JSON-encoded messages for each RPC call
This reveals the precise field values causing type mismatches across the wire.
Inspect the Wire Contract
Compare definitions in runtime/wire.ts against packages/rpc/src/interface.ts. Check for:
- Missing or extra fields in structs
- Incorrect enum values (e.g.,
WireErrorCode) - Union types not narrowed correctly
The Cap'n Proto schema is the source of truth. When it diverges from TypeScript interfaces, runtime failures follow.
Validate Stub Casting
In packages/rpc/src/client.ts, newWebSocketRpcSession performs a double cast:
const stub = new RpcStub(ws, "SyncRPC") as SyncRPC;
This compiles successfully even when the remote side implements a different shape. Add a runtime guard:
import { assertRpcImplements } from "@cloudflare/rpc/debug";
assertRpcImplements(syncStub, "SyncRPC");
// Throws with field-level diffs if shapes diverge
Check Execution-Runtime Tracking
The WorkspaceRuntime class stores execution ID to runtime ID mappings. When assertExecutionRuntime fails, you've hit a cross-runtime boundary violation.
In runtime/runtime.ts, #rememberExecutionRuntime stores the mapping:
// Internal implementation detail from runtime.ts
#rememberExecutionRuntime(execId: string, runtimeId: string): void {
this.#execToRuntime.set(execId, runtimeId);
}
Later calls to assertExecutionRuntime (from runtime/types.ts) verify consistency:
import { assertExecutionRuntime } from "@cloudflare/computer/src/runtime/types";
async function verifyExecutionContext(
execId: string,
expectedRuntimeId: string
) {
const exec = await workspace.runtime.getExec({ id: execId });
// Throws: "expected runtime X but got Y" on mismatch
assertExecutionRuntime("my-operation", expectedRuntimeId, exec.runtimeId);
// Safe to proceed
return exec;
}
Fixes typically involve:
- Passing correct
runtimeIdtoexec,pull, orpush - Ensuring
Workspaceinstances aren't reused across runtimes without resetting#runtime
Run Tests with Native Build
Some type checks only exercise when fuse-native is built:
npm run build # builds all workspace packages
npm test --workspace @cloudflare/computer
Failures appearing only with native builds indicate FUSE shim issues rather than pure-TypeScript problems.
Use the Debug Wrapper for Ad-Hoc Inspection
The debugRpc function wraps any stub with runtime type checking:
import { debugRpc } from "@cloudflare/rpc/debug";
const rawStub = await workspace.getRpcStub();
const safeStub = debugRpc(rawStub); // validates arguments at runtime
await safeStub.pull({ path: "/foo.txt" }); // logs and validates
Fix the Contract
Once identified, update the source of truth:
- Edit
runtime/wire.tsfor Cap'n Proto changes - Adjust
interface.tsdefinitions to match - Regenerate schema:
npm run buildinpackages/rpc - Re-run failing tests
Code Examples for Common Scenarios
Enabling RPC Debug Logging
import { enableRpcDebug } from "@cloudflare/rpc/debug";
enableRpcDebug(); // JSON line per request/response
Adding Runtime Shape Assertions
import { newWebSocketRpcSession } from "capnweb";
import { assertRpcImplements } from "@cloudflare/rpc/debug";
const ws = new WebSocket(url);
const syncStub = newWebSocketRpcSession(ws, "SyncRPC") as SyncRPC;
// Runtime guard against contract drift
assertRpcImplements(syncStub, "SyncRPC");
Verifying Execution-Runtime Consistency
import { assertExecutionRuntime } from "@cloudflare/computer/src/runtime/types";
async function doSomething(execId: string, expectedRuntimeId: string) {
const exec = await workspace.runtime.getExec({ id: execId });
assertExecutionRuntime("operation", expectedRuntimeId, exec.runtimeId);
// Proceed with validated execution context
}
Wrapping Stubs with Debug Validation
import { debugRpc } from "@cloudflare/rpc/debug";
const safeStub = debugRpc(await workspace.getRpcStub());
await safeStub.pull({ path: "/foo.txt" }); // validated at runtime
Key Source Files Reference
Summary
- Cloudflare Computer's type system spans three layers: Cap'n Proto wire contract, runtime implementation, and assertion utilities
enableRpcDebug()surfaces exact RPC payloads causing mismatchesassertRpcImplements()catches stub casting errors before runtime failureassertExecutionRuntime()validates execution context hasn't crossed runtime boundaries- Native builds may expose type issues hidden in pure-TypeScript paths
- Always fix contracts at the source:
wire.tsfor Cap'n Proto,interface.tsfor TypeScript
Frequently Asked Questions
What causes "expected runtime X but got Y" errors in Cloudflare Computer?
This error from assertExecutionRuntime indicates an execution ID was created in one runtime but accessed from another. The WorkspaceRuntime in runtime/runtime.ts tracks exec-to-runtime mappings via #rememberExecutionRuntime. Fix by ensuring you pass the correct runtimeId to exec, pull, or push calls, and don't reuse Workspace instances across different runtimes without resetting the internal #runtime field.
Why does my code compile but fail at runtime with type errors?
The RpcStub proxy in packages/rpc/src/client.ts performs a nominal cast (as SyncRPC) that TypeScript cannot verify across process boundaries. The remote side may implement a different shape while the proxy pretends it's correct. Wrap stubs with debugRpc() or use assertRpcImplements() to add runtime validation that catches these mismatches immediately.
How do I debug type issues that only appear in production?
Enable enableRpcDebug() to log all RPC traffic as JSON, then inspect the exact payloads. Production environments using computerd containers may surface issues masked by worker stubs in development. Run npm run build to include native fuse-native checks, and reproduce failures in packages/computer/tests/runtime.test.ts which exercises real cross-process behavior.
Where should I add new type assertions for custom RPC methods?
Add runtime guards in packages/rpc/src/debug.ts using the existing assertRpcImplements pattern. For execution-specific validation, extend runtime/types.ts following the assertExecutionRuntime implementation. Always update runtime/wire.ts and packages/rpc/src/interface.ts to keep Cap'n Proto contracts synchronized with TypeScript definitions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →