Skip to content

worker: reap expired sandbox workspace backups from R2 - #70

Open
ai-yappa[bot] wants to merge 1 commit into
mainfrom
agent/slack-v1-T07DMLN1JE6-C0BTJCJD69K-1789899954.993629
Open

ai-yappa[bot] wants to merge 1 commit into
mainfrom
agent/slack-v1-T07DMLN1JE6-C0BTJCJD69K-1789899954.993629

Conversation

@ai-yappa

@ai-yappa ai-yappa Bot commented Sep 20, 2026

Copy link
Copy Markdown

Problem

Cloudflare Sandboxes stores every createBackup() as backups/{backupId}/data.sqsh + meta.json in the codevil-sandbox-backups R2 bucket. The ttl option only causes restore-time rejection — the platform does not delete the R2 objects (confirmed in the Backup/Restore guide). The bucket has no lifecycle-expiration rule and the worker never deletes stale archives, so every ~600 MB /workspace snapshot accumulates forever.

Current state (checked via Cloudflare API, Sep 20 2026):

  • 80 objects / 23.47 GB in codevil-sandbox-backups (40 backups), driven by the warm workspace-cache feature
  • ≈13.5 GB over the 10 GB R2 free tier → $0.18/mo and growing
  • The Aug 19 snapshot is already 32 days old — past the intended 30-day WORKSPACE_CACHE_TTL_SECONDS

Change

Add a daily scheduled sweep (scheduled handler + [triggers] crons = ["0 4 * * *"]) that:

  1. Lists backups/ objects in the R2 bucket (paginated, 1000/page)
  2. Deletes objects whose uploaded time is older than the 30-day TTL
  3. Removes matching workspace_snapshots rows from D1 so restores never target a deleted archive

New module: packages/worker/src/backup-reaper.ts (pure selection + sweep orchestration), 7 unit tests in packages/worker/test/backup-reaper.test.mjs, wired into packages/worker/src/index.ts.

Notes

  • Aligned with the existing WORKSPACE_CACHE_TTL_SECONDS (30 days) that createBackup already passes — the sweep enforces the TTL the code believed was being enforced.
  • Not done here: adding an R2 bucket-level lifecycle rule on backups/ is the platform-recommended complement (works without a deploy). It requires Cloudflare API write access, which the sandbox token doesn't have. A follow-up with #billing:read-scoped-plus write creds can add it via PUT /accounts/{account_id}/r2/buckets/codevil-sandbox-backups/lifecycle.

Verification

  • pnpm run build passes
  • packages/worker test suite: 498/498 pass (7 new)

Cloudflare Sandbox createBackup ttl only rejects expired archives at
restore time; the platform never deletes the R2 objects. The
codevil-sandbox-backups bucket had no lifecycle rule and no cleaner, so
every 600MB /workspace snapshot accumulated indefinitely (23.5GB and
counting, 13.5GB over the R2 free tier).

Add a daily scheduled sweep that deletes backups/ objects older than the
30-day WORKSPACE_CACHE_TTL_SECONDS and removes the matching
workspace_snapshots rows from D1 so restores never target a missing
archive.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants