From e23d6dc965c412940c977670e4be49c6b20b7b7d Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Sat, 1 Aug 2026 10:14:13 +0000 Subject: [PATCH 1/2] computer: unify backend connect on a single host-injected signature The shell and module backend interfaces took different connect signatures: the shell kind a bare connect(), the module kind a connect(host) receiving the storage-layer handles the Workspace owns. The split was historical, not necessary; the host bag exists only because the Workspace builds db and fs after the caller constructs the backends. Give WorkspaceBackend.connect the same host parameter and pass the bag on every connect. Shell and container backends ignore it and reach the host through their own transport; the module backend uses it as before. Method-parameter bivariance lets the existing zero-argument implementations satisfy the wider signature unchanged. Collapse the duplicate WorkspaceModuleBackendHost onto the canonical WorkspaceBackendHost so there is one definition. --- packages/computer/src/backend.ts | 22 ++++++++++++++++++++-- packages/computer/src/runtime/types.ts | 9 +-------- packages/computer/src/workspace.ts | 9 ++++++++- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/computer/src/backend.ts b/packages/computer/src/backend.ts index 0156fd2c..ca9b517a 100644 --- a/packages/computer/src/backend.ts +++ b/packages/computer/src/backend.ts @@ -20,6 +20,22 @@ import type { WorkspaceRPC } from "@cloudflare/computer-rpc"; +// The handles the Workspace owns and injects into a backend when +// it connects. The Workspace constructor builds `db`/`fs` itself, +// after the caller has already constructed the backends and passed +// them in, so a backend cannot capture them at its own +// construction time. `connect(host)` is how the Workspace hands +// over the storage-layer handles once they exist. Shell and +// container backends ignore the bag (they reach the host through +// their own transport); the in-process module backend uses it. +export interface WorkspaceBackendHost { + readonly db: import("@cloudflare/dofs").Database; + readonly waitUntil?: (promise: Promise) => void; + readonly fs: import("@cloudflare/dofs").WorkspaceFilesystem; + readonly git: import("./git/index.js").GitClient; + readonly artifacts: import("./artifacts/index.js").ArtifactClient; +} + export interface WorkspaceBackend { // User-supplied selector. Passed in `ExecOptions.backend` and // `Workspace.push(id)` / `Workspace.pull(id)` to address this @@ -48,8 +64,10 @@ export interface WorkspaceBackend { // Materialise a connection. Called lazily on first use, once // per backend per workspace lifetime. The Workspace caches the // resulting handle by `id`; subsequent exec / push / pull - // calls reuse it. - connect(): Promise; + // calls reuse it. The Workspace always passes its host bag; + // backends that reach the host through their own transport + // ignore it. + connect(host: WorkspaceBackendHost): Promise; } export interface BackendHandle { diff --git a/packages/computer/src/runtime/types.ts b/packages/computer/src/runtime/types.ts index 3fe7c341..52d505ce 100644 --- a/packages/computer/src/runtime/types.ts +++ b/packages/computer/src/runtime/types.ts @@ -160,14 +160,7 @@ export interface WorkspaceModuleBackendHandle { close(): Promise; } -export interface WorkspaceModuleBackendHost { - readonly db: import("@cloudflare/dofs").Database; - /** Attach detached backend work to the host event lifetime. */ - readonly waitUntil?: (promise: Promise) => void; - readonly fs: import("@cloudflare/dofs").WorkspaceFilesystem; - readonly git: import("../git/index.js").GitClient; - readonly artifacts: import("../artifacts/index.js").ArtifactClient; -} +export type WorkspaceModuleBackendHost = import("../backend.js").WorkspaceBackendHost; export interface WorkspaceModuleBackend { readonly protocol: "module"; diff --git a/packages/computer/src/workspace.ts b/packages/computer/src/workspace.ts index e8f81c03..3b18e94c 100644 --- a/packages/computer/src/workspace.ts +++ b/packages/computer/src/workspace.ts @@ -857,7 +857,14 @@ export class Workspace { this.#observer, "workspace.connect", { "workspace.backend.id": id, "workspace.backend.type": backend.type }, - () => backend.connect(), + () => + backend.connect({ + db: this.#db, + waitUntil: this.#waitUntil, + fs: this.#fs, + git: this.git, + artifacts: this.#artifacts, + }), ); if (generation !== this.#connectionGeneration) { await handle.close().catch(() => undefined); From 392ae8cc9ae29ca17a06d8047e26f53d96f636d5 Mon Sep 17 00:00:00 2001 From: aron <263346377+aron-cf@users.noreply.github.com> Date: Mon, 3 Aug 2026 09:54:04 +0000 Subject: [PATCH 2/2] computer: guard the unified connect host bag against unconfigured git The connect host bag reaches git through this.git, which throws once git is opt-in and no factory was configured. The module path already routed through the configured-or-disabled guard; the shell and container path, which never used git before the connect unification, did not. Route it through the same guard so a workspace without git keeps running commands. --- packages/computer/src/workspace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/computer/src/workspace.ts b/packages/computer/src/workspace.ts index 3b18e94c..e75ada0f 100644 --- a/packages/computer/src/workspace.ts +++ b/packages/computer/src/workspace.ts @@ -862,7 +862,7 @@ export class Workspace { db: this.#db, waitUntil: this.#waitUntil, fs: this.#fs, - git: this.git, + git: this.#gitFactory ? this.git : DISABLED_GIT_CLIENT, artifacts: this.#artifacts, }), );