the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add backup queues and queue handler

Throw XRPCError when create/restore backup fails.
Add Cloudflare Queue entries to worker wrangler configs.
Implement a queue batch handler that performs backups and persists
backup records to the database.

+374 -21
+1
apps/api/src/xrpc/io/pocketenv/sandbox/createBackup.ts
··· 67 67 ); 68 68 } catch (error) { 69 69 consola.warn("Failed to create backup in sandbox", error); 70 + throw new XRPCError(500, `Failed to create backup ${error}`); 70 71 } 71 72 }; 72 73 server.io.pocketenv.sandbox.createBackup({
+1
apps/api/src/xrpc/io/pocketenv/sandbox/restoreBackup.ts
··· 63 63 ); 64 64 } catch (error) { 65 65 console.warn("Failed to restore backup", error); 66 + throw new XRPCError(500, `Failed to restore backup ${error}`); 66 67 } 67 68 68 69 return {};
+15
apps/cf-sandbox/deploy/amp/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "AMP-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "AMP-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/claude/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "CLAUDE-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "CLAUDE-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/codex/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "CODEX-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "CODEX-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/copilot/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "COPILOT-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "COPILOT-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/crush/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "CRUSH-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "CRUSH-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/cursor/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "CURSOR-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "CURSOR-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/docker/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "DOCKER-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "DOCKER-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/gemini/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "GEMINI-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "GEMINI-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/kilo/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "KILO-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "KILO-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/kiro/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "KIRO-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "KIRO-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/mise/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "MISE-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "MISE-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/nanoclaw/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "NANOCLAW-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "NANOCLAW-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/nix/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "NIX-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "NIX-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/nullclaw/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "NULLCLAW-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "NULLCLAW-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/openclaw/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "OPENCLAW-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "OPENCLAW-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/opencode/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "OPENCODE-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "OPENCODE-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/opencrust/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "OPENCRUST-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "OPENCRUST-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/picoclaw/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "PICOCLAW-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "PICOCLAW-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/pkgx/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "PKGX-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "PKGX-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/wasmer/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "WASMER-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "WASMER-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+15
apps/cf-sandbox/deploy/zeroclaw/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "ZEROCLAW-BACKUP", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "ZEROCLAW-BACKUP", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
+31 -1
apps/cf-sandbox/src/index.ts
··· 6 6 import { cpRoutes } from "./routes/cp"; 7 7 import { sandboxRoutes } from "./routes/sandboxes"; 8 8 import { terminalRoutes } from "./routes/terminal"; 9 + import { backups } from "./schema"; 10 + import dayjs from "dayjs"; 11 + import { createSandbox } from "./providers"; 12 + import { getConnection } from "./drizzle"; 9 13 10 14 export { saveSecrets, saveVariables, getSandboxRecord as getSandboxById } from "./lib/sandbox-helpers"; 11 15 12 16 type Bindings = { 13 17 Sandbox: DurableObjectNamespace<Sandbox<Env>>; 18 + BACKUP_QUEUE: Queue; 14 19 }; 15 20 16 21 const app = new Hono<{ Variables: Context; Bindings: Bindings }>(); ··· 42 47 43 48 export { Sandbox } from "@cloudflare/sandbox"; 44 49 45 - export default app; 50 + export default { 51 + fetch: app.fetch, 52 + async queue(batch: MessageBatch<{ 53 + directory: string; 54 + description?: string; 55 + ttl?: number; 56 + sandboxId: string; 57 + recordId: string; 58 + }>, env: Env) { 59 + const db = getConnection(); 60 + for (const message of batch.messages) { 61 + const params = message.body; 62 + const sandbox = await createSandbox('cloudflare', { id: params.sandboxId }); 63 + const backupId = await sandbox.backup(params.directory, params.ttl); 64 + 65 + await db.insert(backups).values({ 66 + backupId, 67 + sandboxId: params.recordId, 68 + directory: params.directory, 69 + description: params.description, 70 + expiresAt: params.ttl ? dayjs().add(params.ttl, "second").toDate() : dayjs().add(3, "days").toDate(), 71 + }) 72 + .execute(); 73 + } 74 + }, 75 + };
+6 -18
apps/cf-sandbox/src/routes/sandboxes.ts
··· 36 36 import dayjs from "dayjs"; 37 37 import { RestoreParams } from "../types/restore"; 38 38 39 - type Bindings = { Sandbox: DurableObjectNamespace<Sandbox<Env>> }; 39 + type Bindings = { Sandbox: DurableObjectNamespace<Sandbox<Env>>, BACKUP_QUEUE: Queue; }; 40 40 type App = { Variables: Context; Bindings: Bindings }; 41 41 42 42 export const sandboxRoutes = new Hono<App>(); ··· 539 539 540 540 const params = await c.req.json<BackupParams>(); 541 541 542 - const sandbox = await createSandbox("cloudflare", { id: record.sandboxId! }); 543 - 544 - c.executionCtx.waitUntil( 545 - (async () => { 546 - const backupId = await sandbox.backup(params.directory, params.ttl); 547 - 548 - await c.var.db.insert(backups).values({ 549 - backupId, 550 - sandboxId: record.id, 551 - directory: params.directory, 552 - description: params.description, 553 - expiresAt: params.ttl ? dayjs().add(params.ttl, "second").toDate() : dayjs().add(3, "days").toDate(), 554 - }) 555 - .execute(); 556 - })() 557 - ); 558 - 542 + await c.env.BACKUP_QUEUE.send({ 543 + ...params, 544 + recordId: record.id, 545 + sandboxId: record.sandboxId!, 546 + }); 559 547 560 548 return c.json({}); 561 549 });
+5 -2
apps/cf-sandbox/worker-configuration.d.ts
··· 1 1 /* eslint-disable */ 2 - // Generated by Wrangler by running `wrangler types` (hash: 007951150813dda1c7519cd16fb68047) 2 + // Generated by Wrangler by running `wrangler types` (hash: 11a07eaf25a469df44e96967b5b9da13) 3 3 // Runtime types generated with workerd@1.20260401.1 2025-05-06 nodejs_compat 4 4 declare namespace Cloudflare { 5 5 interface GlobalProps { ··· 10 10 POCKETENV_COPY: R2Bucket; 11 11 BACKUP_BUCKET: R2Bucket; 12 12 HYPERDRIVE: Hyperdrive; 13 + BACKUP_QUEUE: Queue; 13 14 SANDBOX_TRANSPORT: "websocket"; 14 15 PREVIEW_TOKEN: "sandbox"; 16 + CLOUDFLARE_ACCOUNT_ID: "fe5b1e2ce9f94f4c0415ab94ce402012"; 17 + BACKUP_BUCKET_NAME: "pocketenv-backup"; 15 18 CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE: string; 16 19 PUBLIC_KEY: string; 17 20 PRIVATE_KEY: string; ··· 28 31 [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; 29 32 }; 30 33 declare namespace NodeJS { 31 - interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "SANDBOX_TRANSPORT" | "PREVIEW_TOKEN" | "CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE" | "PUBLIC_KEY" | "PRIVATE_KEY" | "JWT_SECRET" | "VOLUME_BUCKET" | "ACCOUNT_ID" | "R2_ACCESS_KEY_ID" | "R2_SECRET_ACCESS_KEY">> {} 34 + interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "SANDBOX_TRANSPORT" | "PREVIEW_TOKEN" | "CLOUDFLARE_ACCOUNT_ID" | "BACKUP_BUCKET_NAME" | "CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE" | "PUBLIC_KEY" | "PRIVATE_KEY" | "JWT_SECRET" | "VOLUME_BUCKET" | "ACCOUNT_ID" | "R2_ACCESS_KEY_ID" | "R2_SECRET_ACCESS_KEY">> {} 32 35 } 33 36 34 37 // Begin runtime types
+15
apps/cf-sandbox/wrangler.jsonc
··· 25 25 "bucket_name": "pocketenv-backup", 26 26 }, 27 27 ], 28 + "queues": { 29 + "producers": [ 30 + { 31 + "queue": "BACKUP-QUEUE", 32 + "binding": "BACKUP_QUEUE", 33 + } 34 + ], 35 + "consumers": [ 36 + { 37 + "queue": "BACKUP-QUEUE", 38 + "max_batch_size": 10, 39 + "max_batch_timeout": 60, 40 + } 41 + ], 42 + }, 28 43 /** 29 44 * Smart Placement 30 45 * Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement