Skip to content

revert(sandbox): remove shared agent sandboxes and the staging default - #5240

Merged
pedrofrxncx merged 2 commits into
mainfrom
claude/revert-shared-sandboxes
Jul 27, 2026
Merged

revert(sandbox): remove shared agent sandboxes and the staging default#5240
pedrofrxncx merged 2 commits into
mainfrom
claude/revert-shared-sandboxes

Conversation

@tlgimenes

@tlgimenes tlgimenes commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Reverts #5112 (share agent sandboxes by branch), #5116 (always share agent sandboxes) and #5132 (use shared staging branch), restoring the pre-#5112 behaviour: hosted agent sandboxes are per-user again, and a new GitHub-backed thread gets a generated branch instead of staging.

This is R1 (code only). The two tables stay in place — see Migrations and Deploy order. It must not ship before the drain (below).

Why it isn't a git revert

None of the three commits reverse-apply. Measured against origin/main: reverting #5112 alone produces 33 conflicted paths, and resurrects three files at the dead apps/mesh/ path.

So each file was reconstructed against its current path, using git show 5b0d71784:<old-path> as the specification, then diffed back against that baseline to prove convergence.

The load-bearing change

SandboxId returns to a flat { userId, projectRef } and sandboxIdKey to `${userId}:${projectRef}`. The claim handle is slugify(branch)-sha256(idKey).slice(0,16), so every live shared claim gets a different address the moment this deploys.

  • t=0 — split brain, not an outage: the old hostname still resolves and serves the old shared pod, while each user's next start provisions a second per-user pod on the same git branch. ~2× pod count.
  • t≈+15min — the operator's idle TTL reaps the orphaned claim + Service. Its HTTPRoute has no ownerReferences and is only removed by deleteHttpRouteIfManaged, whose call path this PR deletes → the Route leaks and 502s.

Uncommitted work is mostly safe: the daemon registers SIGTERM → shutdown() → publish(...) (packages/sandbox/daemon/entry.ts:919-960, covered by daemon.git.e2e.test.ts:174), and the operator's teardown is an ordinary Delete → SIGTERM within the grace period.

Required before deploy

  1. Quiesce new starts, then wait for every provisioning row to reach ready so it has a handle.
  2. Drain every agent_sandbox_sessions row on the current code (the teardown paths are deleted by this PR). Note SANDBOX_DELETE marks rows terminal even when it deleted nothing — a NULL-handle row skips runner.delete, and a rejection is swallowed. "Zero non-terminal rows" is not a completion signal; only the kubectl check is.
  3. Sweep leaked routes: kubectl -n <ns> delete httproute -l 'studio.decocms.com/role=claimed,studio.decocms.com/org-id,!studio.decocms.com/user-id' (the org-id term matters — !user-id alone also matches tenant-less internal sandboxes).
  4. Sweep stale sandboxMap entries. fix(sandbox): stop trusting legacy agent-sandbox entries on the agent row #5225's message records ~1789 leftover agent-sandbox cells on agent rows. They're inert today; after this PR they are authoritative again and point at long-reaped claims. Strip the agent-sandbox and legacy cluster keys from each branch cell (keep user-desktop) on the old code, where it's behaviourally a no-op.

Migrations

Both migrations are pure CREATE TABLE — neither alters a pre-existing table, so nothing is lossy. They are kept, byte-identical, along with their index.ts entries.

Deleting them would crash-loop every pod. In kysely@0.28's migrator.js, #ensureNoMissingMigrations runs at :447 — unconditionally, outside the allowUnorderedMigrations gate at :448 — and throws corrupted migrations: previously executed migration ... is missing at :495. So allowUnorderedMigrations: true would not help. Migrations run on boot (settings/pipeline.ts:50); nothing under deploy/ or selfhost/ sets --skip-migrations.

CI would not catch it: storage-integration.yml migrates a fresh Postgres from an empty ledger, and migrations/index.test.ts only checks file↔registration parity, so deleting file and entry together passes both.

The tables are dropped by a follow-up forward migration 145-drop-agent-sandbox-tables.ts in R2, after R1 has fully rolled out. Worth doing rather than leaving them dormant: agent_sandbox_runner_state.state holds plaintext credentials (an x-access-token:ghs_… clone URL, submodule tokens, the env bag) and has no organization FK, so nothing ever GCs it.

Deploy order

R1 = this PR (tables left in place, harmless — nothing reads them). R2 = migration 145. Same-release drops mean an old pod hits relation "agent_sandbox_sessions" does not exist on every SANDBOX_START; the chart runs migrations from the app CMD, so there's no natural barrier. Two releases also buy a rollback window.

Also reverted

These exist only inside the shared model:

PR Note
#5225 The dangerous one. withoutLegacyAgentSandboxEntries strips exactly the agent-sandbox sandboxMap entries the reverted server writes back, while the session graft that compensated is gone — vmEntry permanently null → shouldAutoStart permanently true → SANDBOX_START on every mount and branch switch. It compiles fine; nothing but a deliberate decision catches it.
#5219 resolveSharedThreadVm + the shared-scope branch. Its hoist of the unconditional thread-map cleanup is kept — that bug is scope-independent and pre-dates sharing.
#5183 Stale-provisioning reset; lived inside the deleted storage class.

Deliberately preserved

Testing

  • bun run check, bun run lint (0 errors), bun run fmt — clean.
  • Sandbox unit tests: 499 pass, 0 fail.
  • apps/web: 23 failures — the identical set as origin/main (pre-existing); the 6-test delta is exactly the deleted agent-sandbox-map.test.ts.
  • apps/api + packages: 245 pre-existing failures on both branches (integration tests needing Postgres/NATS). 0 new; 1 gone — the deleted shared-sandbox integration test.
  • Convergence: every reconstructed file diffed against its 5b0d71784 blob. Residuals are only refactor(studio): split API and web workspaces #5120 specifier rewrites plus the preserved work listed above.
  • Tests inverted rather than appended, per the repo checklist: start.test.ts asserts { userId, projectRef } again, handle.test.ts drops the shared/user isolation cases, runtime-defaults.test.ts drops the staging/main assertions, and chat-locked-thread.spec.ts is back to the pickWarmBranchFromSandboxMap → generateBranchName() wording.

Not run locally: real-Postgres integration tests and the Playwright e2e suite — CI covers both.

Open items

  1. Threads pinned to branch = 'staging' (persisted column, storage/types.ts:945/:1032). Post-revert two members get separate pods on the same branch, and the second non-force git push -u origin staging is rejected non-fast-forward — a failure mode that didn't exist pre-feat(sandbox): use shared staging branch #5132, when every thread had a unique generated branch. Do not null the column (that orphans the origin/staging work and breaks existingThread?.branch ?? input.branch). Either accept with a release note, or rewrite to per-thread names after pushing each staging workspace. Needs a decision + the row counts.
  2. Are fix(sandbox): force-reprovision a provisioning session orphaned by a crashed start #5183/fix(sandbox): emit gone for orphaned thread sandboxes on shared scope #5219/fix(sandbox): stop trusting legacy agent-sandbox entries on the agent row #5225 intentionally lost? They fixed real symptoms ("preview stuck on Reserving sandbox", "works for me, stuck for you"). Confirm they can't recur in the per-user model.
  3. 404 grace: useAgentSandboxSession polled /api/:org/agent-sandbox-sessions/:vmid every 5s. Already-loaded tabs will 404 until reload. Optionally leave the route mounted returning an empty list for one release.
  4. Colliding open PRs on the branch-selection surface: fix(chat): New chat always creates, on the current branch #5231, feat(cms): fast preview via preview server + branch-aware new chat #5215, feat(header): branch label, truncation priority, and icon-only actions below lg #5230, feat(web): move branch selector into the toolbar breadcrumb #4556.
  5. Branch protection on staging? The daemon's protectedBranches() is {main, master, origin/HEAD default}, so staging was freely pushable — if a customer has since protected it, post-revert publishes fail differently.

🤖 Generated with Claude Code


Summary by cubic

Reverts shared agent sandboxes and the staging default. Sandboxes are per-user again, and new GitHub-backed threads pick a warm branch or get a generated name.

  • Refactors

    • Restores per-user identity: SandboxId is { userId, projectRef } and sandboxIdKey is userId:projectRef. computeClaimHandle now hashes userId:projectRef, so existing shared handles will change.
    • Removes the shared session model: deletes the agent-sandbox-sessions API/routes/storage and related client hooks; provider resolution no longer looks up shared sessions and uses the acting user’s sandboxMap instead.
    • Branch defaults: thread creation now picks the most-recently-touched branch from the user’s sandboxMap, falling back to generateBranchName; the fixed staging/main defaults are removed; daemon base-branch logic reads origin/HEAD and validates remote ref names.
    • Tooling and UI: drops shared-only secret restrictions; dev-connection resolves only to the acting user’s sandbox; branch picker/use-branches no longer depend on shared sessions; adds readCachedTaskBranch to carry the active task’s branch into new threads; keeps fix(chat): New chat always creates, on the current branch #5231’s caller-passed branch param path; generates a branch on publish when missing.
  • Migration

    • Keep the shared-session tables and migration entries in place for now to avoid migrator errors; a follow-up migration will drop them.
    • Before deploy, drain existing shared agent sandboxes so leaked routes and double provisioning don’t occur; sweep any orphaned HTTP routes after the drain.
    • Expect claim handles to change on first start post-deploy, provisioning per-user pods on the same branch.

Written for commit d0c070b. Summary will update on new commits.

Review in cubic

tlgimenes and others added 2 commits July 24, 2026 23:55
Reverts #5112 (share agent sandboxes by branch), #5116 (always share
agent sandboxes) and #5132 (use shared staging branch), restoring the
pre-#5112 behaviour: hosted agent sandboxes are per-user again, and a
new GitHub-backed thread gets a generated branch instead of `staging`.

Reconstructed against current paths rather than reverted: #5120 split
apps/mesh into apps/api + apps/web and hoisted branch-name.ts,
runtime-defaults.ts and thread/schema.ts into packages/shared, and #5174
deleted vm-events.ts outright, so none of the three commits reverse-apply.

Core change: SandboxId goes back to a flat `{ userId, projectRef }` and
sandboxIdKey back to `userId:projectRef`, so every hosted claim handle
changes. Live shared claims must be drained BEFORE this deploys.

Also reverted, because they only exist inside the shared model:
- #5225 withoutLegacyAgentSandboxEntries — left in place it would strip
  the sandboxMap entries the reverted server writes back, leaving
  shouldAutoStart permanently true (silent, compiles fine)
- #5219 resolveSharedThreadVm and the shared-scope branch
- #5183 stale-provisioning reset (lived inside the deleted storage)

Deliberately preserved:
- migrations 137/138 and their index.ts entries — Kysely's
  #ensureNoMissingMigrations runs unconditionally, so deleting an applied
  migration crash-loops every pod. The tables are dropped by a follow-up
  forward migration after the drain, not by this commit.
- #5114/#5118 withClaimGitLock, #5126 org-context guard, #5143 analytics
  gate, #5171 owner-keyed thread graft, #5195/#5207/#5210/#5221 client
  auto-retry, the daemon status-code train and the settings-validation train
- #5112's org-fs least-privilege narrowing and the start dedup key fix
- #5116's squashed queue-tray pluralization (en + pt-br)
- the remote-branch-name validations #5132 deleted, since this restores
  the dynamic origin/HEAD lookup that made them load-bearing

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolved 3 conflicts toward main's post-branch work:
- use-layout-state.ts / shell-layout.tsx: keep #5231's caller-passed
  `branch` param over the branch's parallel readCachedTaskBranch path.
  main's callers already omit the branch on agent-switch, which is the
  same guarantee the branch's carryBranch gate provided.
- connection-tools.integration.test.ts: keep userModelPreferences stub,
  drop agentSandboxSessions (storage class removed by the revert).
@pedrofrxncx
pedrofrxncx merged commit 5bfa850 into main Jul 27, 2026
16 checks passed
@pedrofrxncx
pedrofrxncx deleted the claude/revert-shared-sandboxes branch July 27, 2026 16:51
decocms Bot pushed a commit that referenced this pull request Jul 27, 2026
PR: #5240 revert(sandbox): remove shared agent sandboxes and the staging default
Bump type: patch

- decocms (apps/api/package.json): 4.133.1 -> 4.133.2
- @decocms/e2e (packages/e2e/package.json): 1.22.0 -> 1.22.1
- @decocms/sandbox (packages/sandbox/package.json): 1.22.9 -> 1.22.10
- @decocms/shared (packages/shared/package.json): 0.4.0 -> 0.4.1
- deploy/helm/sandbox-env (chart 0.9.28) (deploy/helm/sandbox-env/values.yaml deploy/helm/sandbox-env/Chart.yaml): image.tag/appVersion -> 1.22.10

Deploy-Scope: both
pedrofrxncx added a commit that referenced this pull request Jul 27, 2026
…5240) (#5269)

#5240 restored per-user hosted sandboxes and deleted every reader/writer of
agent_sandbox_sessions and agent_sandbox_runner_state, but deliberately kept
both tables so the rollout had a rollback window. This is R2: the forward
migration that drops them.

agent_sandbox_runner_state.state holds plaintext credentials (an
x-access-token:ghs_... clone URL, submodule tokens, the resolved env bag) and
the table has no organization FK, so nothing ever GCs a row -- dormant rows
are a standing secret leak, not just dead weight.

Co-authored-by: Pedro França <pedrofrxncx@deco.cx>
decocms Bot pushed a commit that referenced this pull request Jul 27, 2026
PR: #5269 refactor(storage): drop the dead shared agent-sandbox tables (R2 of #5240)
Bump type: patch

- decocms (apps/api/package.json): 4.133.2 -> 4.133.3

Deploy-Scope: server
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.

2 participants