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.

Expose sandbox ports and tolerate failures

Make BaseSandbox.expose return string | null and handle exceptions in
the Cloudflare provider so failed exposes don't crash startup. Query
sandbox_ports when starting a sandbox and expose each port using the
request hostname.

+23 -8
+14 -4
apps/cf-sandbox/src/index.ts
··· 6 6 files, 7 7 sandboxes, 8 8 sandboxFiles, 9 + sandboxPorts, 9 10 sandboxSecrets, 10 11 sandboxVariables, 11 12 sandboxVolumes, ··· 292 293 .leftJoin(users, eq(sandboxes.userId, users.id)) 293 294 .where(eq(sandboxVolumes.sandboxId, c.req.param("sandboxId"))) 294 295 .execute(), 296 + c.var.db 297 + .select() 298 + .from(sandboxPorts) 299 + .leftJoin(sandboxes, eq(sandboxPorts.sandboxId, sandboxes.id)) 300 + .leftJoin(users, eq(sandboxes.userId, users.id)) 301 + .where(eq(sandboxPorts.sandboxId, c.req.param("sandboxId"))) 302 + .execute(), 295 303 ]); 296 304 297 305 await sandbox.setEnvs({ ··· 316 324 }); 317 325 318 326 await sandbox.sh`[ -f /root/.ssh/id_ed25519 ] || ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -q -N "" || true`; 327 + 328 + const { hostname } = new URL(c.req.url); 319 329 320 330 await Promise.all([ 321 331 ...params[2] ··· 339 349 volume.sandbox_volumes.path, 340 350 `/${volume.users?.did || ""}${volume.users?.did ? "/" : ""}${volume.sandbox_volumes.id}/`, 341 351 ), 352 + ), 353 + ...params[6].map((port) => 354 + sandbox?.expose(port.sandbox_ports.exposedPort, hostname), 342 355 ), 343 356 ]); 344 357 ··· 512 525 } 513 526 } 514 527 515 - const sandbox = getSandbox( 516 - c.env.Sandbox, 517 - record.name, 518 - ); 528 + const sandbox = getSandbox(c.env.Sandbox, record.name); 519 529 const sessionId = c.req.query("session"); 520 530 521 531 const cfsandbox = await createSandbox("cloudflare", {
+8 -3
apps/cf-sandbox/src/providers/cloudflare/index.ts
··· 120 120 return this.sandbox.unmountBucket(path); 121 121 } 122 122 123 - async expose(port: number, hostname: string): Promise<string> { 124 - const { url } = await this.sandbox.exposePort(port, { hostname }); 125 - return url; 123 + async expose(port: number, hostname: string): Promise<string | null> { 124 + try { 125 + const { url } = await this.sandbox.exposePort(port, { hostname }); 126 + return url; 127 + } catch (e) { 128 + console.log("Failed to expose port", e); 129 + } 130 + return null; 126 131 } 127 132 128 133 async unexpose(port: number): Promise<void> {
+1 -1
apps/cf-sandbox/src/providers/index.ts
··· 12 12 abstract clone(repoUrl: string): Promise<void>; 13 13 abstract mount(path: string, prefix?: string): Promise<void>; 14 14 abstract unmount(path: string): Promise<void>; 15 - abstract expose(port: number, hostname: string): Promise<string>; 15 + abstract expose(port: number, hostname: string): Promise<string | null>; 16 16 abstract unexpose(port: number): Promise<void>; 17 17 } 18 18