Repository navigation
refactor(cloud): boot timing off the workspace row; repositories unordered; provision traced - #7551
Conversation
…dered; provision traced The five boot-stamp columns are dropped (migration 0119) and the desktop's cloud_workspace_opened event, its create-start record and the terminal attach notifier are removed with the measure script: the P0 study is done and its numbers are in the plan. The provision job is now one Sentry transaction with a span per stage (claim, create, settle), sampled by name so nothing else in the API is traced, flushed by the job itself since it runs after its request has answered, and a structured log line records the same durations in Vercel whether or not the span arrives. position leaves both repository link tables (migration 0120): repositories read alphabetically everywhere and the workspace opens on the environment's config location, else the first by name; the workspace-repositories endpoint marks that one primary. Multi-repository workspaces opening at the root with a sidebar picker is a recorded TODO. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
The six incremental migrations sandbox-v2 accumulated (boot stamps added then dropped, position added then dropped, a cascade rebuilt) are regenerated as one, 0115_cloud_environments_repositories, before any of them reach main; a second generate finds no changes. The dev Neon branch's journal was rewritten to the single entry after its six recorded hashes matched the six old files and its schema matched the new migration. The provision job's structured log line is gone: the Sentry transaction is the record, and whether it arrives is checked after the production deploy. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP
…anch environments.hooks held one value anything read back: the internal environment's start hook. It is now the monorepo's own cloud-only `start` key in .superset/config.json, the release still runs internal-setup.sh itself, and the identity's SUPERSET_SANDBOX_HOOKS goes with the column. The release builds its golden from SUPERSET_INTERNAL_BRANCH (default main) so a branch can be released before it merges. cloud_workspace_repositories.branch only fed a box's first checkout and went stale after. The primary checks out the workspace's own branch and the rest their default branch, both read when the box is claimed. The branch migration is regenerated again as one file; the dev Neon branch had both columns dropped and its journal entry repointed. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP
…ts, brokered credentials (#7548) * docs(sandbox): record the sandbox layout decisions, timing plan and build checklist Twenty-four decisions on how a cloud workspace sandbox is laid out, provisioned, credentialed and updated, taken directory by directory against the reference design; the start-time plan they serve; and the per-PR build and test checklist the sandbox-v2 branch follows. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * feat(sandbox): measure every stage of a cloud workspace boot (P0) (#7527) One trace per cloud workspace boot, from cloudWorkspace.create to the first host-service 200 and the desktop's first terminal, and a script that turns five creates and five reopens into a stage table. The start-time plan's estimates are replaced with what it measured. start.sh stamps every phase to /data/boot.log with millisecond epochs and host-service reports the current boot's stamps and its runtime on health.check in sandbox mode, adding its own process-start and listening marks. The provisioning job writes its clock onto the cloud_workspaces row (job start, sandbox create start and end, boot fired) with `ready`, and the first wake that sees host-service answer writes first_healthy_at and nudges the list. The desktop emits cloud_workspace_opened once per open with create→ready, ready→access, ready→first-200 and ready→first-terminal, plus the row's job-side deltas; a terminal attach broadcast in the transport gives it the last of those. `bun run sandbox:measure` runs the job in-process against the newest fork environment, probes health every 100 ms beside the API's own one-second poll, stops and wakes each sandbox, prints medians and maxes and deletes what it created. Measured against a rebuilt dev golden: create to first 200 is 19.6 s median, of which the boot script is 2 s (the checkout) and host-service 1.1 s; the first SDK call after Sandbox.fork blocks about 15 s in most boots, and a resume is about 15 s regardless. Reopen to first 200 is 15.4 s median. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * feat(sandbox): the box as a bundle — packages/sandbox, the ubuntu user, the boot runner, the env push and header-rule credentials (#7528) * feat(sandbox): the box as a bundle: packages/sandbox, the ubuntu user, the boot runner and the env push A cloud workspace's box is now produced by packages/sandbox: an image that carries the OS, toolchain and desktop packages, and a content-addressed bundle (rootfs overlay, hashed assets from cdn.superset.sh, versioned steps) the box installs and updates itself from at boot. The control plane writes one identity file, starts superset-boot through the sandbox API with the host secret in that command's env, and pushes the environment's variables into host-service after boot; every outbound credential, GitHub included, is a firewall header rule the box never sees. host-service, the desktop, the checkout and every hook run as ubuntu; the desktop stream is websockify on its own published port with its own gate ticket, and host-service no longer proxies VNC. The release rebuilds the runtime asset and the golden, probes a fork (including a stop and a wake) and writes the environment rows only after the probe passes; environments gain a bundle pin and hook overrides (migration 0115). CI boots the image twice on every sandbox PR. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(sandbox): boot through sudo with the secret preserved, log a missing runtime, and phase-token stamps Vercel's own sudo option resets the env, so the host secret never reached the boot runner on a real box; the image's sudoers now grants SETENV and the control plane runs sudo --preserve-env itself. The boot-twice test boots the same way. A missing host-service runtime is logged rather than refused so the desktop still comes up. The runner's fail-open sentinel no longer silences steps before a disabled one, and a new version of the disabled step retries it; runner-check.ts covers the runner against a synthetic bundle and runs in CI. Boot stamps are dotted phase tokens so health.check can report them. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * test(host-service): the pushed environment reaches new terminals only Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * style(sandbox): format the boot-twice regex Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * chore(sandbox): drop a scratch inspection script that slipped into the tree Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * chore(sandbox): host-service asset row from the merged runtime Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(sandbox): the setup hook runs with no USER in its env Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(sandbox): give the dev gate a per-port origin so a workspace's two tickets never share a URL Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * ci(sandbox): the bucket client needs CDN_URL too Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(sandbox): the release probe finds the dev stack on the env file's port; a golden's logs are stripped before its snapshot Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(sandbox): the visible Chrome profile gets its own directory, since Chrome 136+ refuses remote debugging on the default one The desktop entry now runs the wrapper instead of copying its flags, and the login profile turns the agent CLIs' self-updater off (installed by root at image build, it had nowhere to write). Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * docs(sandbox): record the real-box and desktop-pane evidence Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * feat(sandbox): terminals inherit the desktop session's bus address The reference captures its session env for shells; the desktop init now starts the session bus first and writes its address for the login profile to source, so a GUI app launched from a terminal joins the session. Release and rollback evidence recorded. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * perf(sandbox): one call to boot a box; a create settles instead of re-waking A wake paid the platform's resume on writeFiles and then made three more sequential calls. The identity now rides in the boot command's env beside the host secret and the runner writes the file itself; the policy update goes out together with that command; the health poll runs at 100 ms; and a create, whose box is already booting, only waits and pushes the environment instead of re-running the wake. The provisioning stamps that the merge dropped are back on the row. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * docs(sandbox): the runner already on the box handles the boot, so runner-input changes ship with the image Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * docs(sandbox): the v2 layout's stage table beside the P0 baseline Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(cloud-workspace): one guarded status transition; a delete during provisioning wins Every status write goes through transitionCloudWorkspace, which only moves a row out of the states it may leave. The provisioning job marks ready only from provisioning and tears its box down when a delete got there first; a readiness timeout keeps the box for diagnosis. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(host-service): name the managed-env module file, since Node cannot import the directory Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * style(host-service): format the managed-env import Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * revert(sandbox): the identity is a file the API writes before boot (#7534) Satya chose the file over the boot command's env: what a person reads on the box stays legible, it has no size ceiling, and the runner's inputs keep one shape; the extra provider call is about 0.3 s on a 15 s path. The policy update still goes out alongside the write, and a create still settles instead of re-waking. The box now receives only the start and ports hook overrides; setup is the release's and could be a whole script. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * feat(sandbox): the runner sequences the start hook; a failed box is kept and stopped (#7535) * feat(sandbox): the boot runner sequences the start hook; host-service runs it on request Settled with Satya after the fact: the environment the hook needs is pushed into host-service and never leaves it, so host-service spawns the hook; the runner decides when, once host-service answers, the push has landed and the checkout is in, through sandbox.runStartHook, so every boot-time action reads in one log. boot-twice pushes an environment and checks the log's hook line. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * feat(cloud-workspace): a box whose host-service never answered is kept and stopped Settled with Satya after the fact: the boot log, desktop and shell stay a resume away for diagnosis, and the box costs storage rather than compute until someone looks or deletes it. Any other provisioning failure still deletes the box. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * feat(environments): multi-repository environments, scope and the creation dialog (#7538) * feat(environments): multi-repository environments, scope and the creation dialog An environment lists its repositories in order (the first is the one a workspace opens on), names which checkout's .superset/config.json it acts on, and is scoped to the organization or to its creator. A workspace fixes its checkouts at create; the box lays every repository out under /workspace/<name>, host-service seeds a project per checkout, and the firewall carries one installation token scoped to exactly those repositories. The desktop's environment dialog takes name, repositories (with a resync), config location and scope, and can hand a new environment to an agent that installs what the project needs and writes its hooks. The new-workspace form picks repositories for an environment that fixes none, and reads branches from the primary repository. Mobile reads the same rows. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * feat(environments): the cloud form's base branch, the asset fetch around a cached 404, and the records The base-branch pill now renders for a cloud workspace and its choice is the branch the box checks out (mobile already sent one); an untyped choice lets the API resolve the primary repository's default. The host-service asset moves to this checkout's build. The runner's asset fetch retries with a cache-busting query when the plain content-addressed URL answers 404: the CDN caches a 404 for four hours, so an image build that ran before the publish poisoned the edge. Decisions 25 and 26, the checklist's PR 6 with the CDP and real-box evidence, and the layout mismatch are recorded. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * test(host-service): the self-seed test carries the multi-repository identity The integration test built a single-checkout identity by hand; it now lists two repositories and expects the primary under the cloud workspace id and the sibling under the derived one. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(db): a workspace's repository rows go with the repository (#7540) `cloud_workspace_repositories.repository_id` had no delete rule, so disconnecting the GitHub integration (which deletes the installation and its repositories) was blocked by any cloud workspace that had checked one out. Migration 0118 (drizzle-kit) rebuilds the constraint with cascade, as `environment_repositories` already had. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(sandbox): the managed env reaches host-service's spawns; install-host takes the bundle's tarball (#7541) * fix(host-service): the managed environment reaches host-service's own spawns The control plane's push only reached terminals; gh, git, the credential helper and anything else host-service spawns itself ran on the login-shell snapshot, so on a sandbox `gh` refused to call GitHub for lack of the placeholder token even though the firewall rule was in place. The push now mirrors into the process environment (a key dropped by the next push leaves it) and the tool environment merges the managed set over the cached shell snapshot, the way terminals already did. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(sandbox): install-host takes the tarball this bundle declares; sync-assets prunes stale media On a box woken onto a bundle with a new host-service, `install-host` ran and left the old build current: it took the first `host-service-*.tar.gz` in the media directory, and the earlier build's tarball was still staged beside the new one. The step now picks the archive whose hash the bundle's manifest lists, and `sync-assets` prunes any staged archive the manifest no longer names before it fetches. runner-check covers the prune; the checklist records the live wake that proved the flow and gh through the firewall. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(api): the GitHub install callback runs the initial sync directly in development (#7542) The queue cannot reach a localhost API, so a dev install ended with sync_queue_failed and no repositories; triggerSync already calls the job endpoint directly in development, and the callback now does the same. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * feat(sandbox): a lone repository is the workspace root; a promoted environment's repositories are fixed (#7544) * feat(sandbox): a lone repository is the workspace root; a promoted environment's repositories are fixed A workspace on one repository has it at /workspace, as the reference machine had; several sit at /workspace/<name>. The path is data on the workspace row ("." for the root), so the runner, the seeding and the hooks read it rather than count. Decided with Satya over "always /workspace/<name>". An environment that has a golden cannot change its repositories: the golden was cloned, set up and snapshotted for that set, a repository added later would be a bare clone with no setup, and under the root layout a sibling would land inside the first checkout. update refuses it and the dialog shows the set read-only with "promote again to change". Image-backed environments change freely. An update that changed only the repositories used to throw "No values to set" after writing them. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * docs(sandbox): the root-layout boxes and the freeze on the checklist Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(sandbox): the runner clones beside the target and moves the checkout into place host-service seeds the workspace row before the checkout lands and starts git activity in that directory; on the monorepo golden it raced the runner's clone into the same path (shallow.lock held by another process, packs vanishing mid-fetch) and the checkout never finished. The clone now lands in a sibling temporary directory and is moved in, the worktree first and .git last, so the directory becomes a repository in one rename; the directory itself is emptied rather than replaced so a shell sitting in it keeps its cwd. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * docs(sandbox): the clone race on the checklist Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * docs(sandbox): the golden rebuild on the root layout Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(cloud): the settings cloud pages and mobile's environments query follow the cloud flag (#7549) * fix(cloud): the settings cloud pages and mobile's environments query follow the cloud flag The Settings "Environments" and "Agents" entries were offered to every v2 user and mobile queried environments for every signed-in user; the server refuses both without the cloud-workspaces flag, so they showed an error state instead of nothing. Both now check the same flag the sidebar and the device picker do. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(cloud): environment rows without repositories never throw into a render A client newer than the API would receive environment rows without the repositories field; the desktop form, the settings list and the mobile widget read it null-safely now. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * refactor(cloud): boot timing off the workspace row; repositories unordered; provision traced (#7551) * refactor(cloud): boot timing off the workspace row; repositories unordered; provision traced The five boot-stamp columns are dropped (migration 0119) and the desktop's cloud_workspace_opened event, its create-start record and the terminal attach notifier are removed with the measure script: the P0 study is done and its numbers are in the plan. The provision job is now one Sentry transaction with a span per stage (claim, create, settle), sampled by name so nothing else in the API is traced, flushed by the job itself since it runs after its request has answered, and a structured log line records the same durations in Vercel whether or not the span arrives. position leaves both repository link tables (migration 0120): repositories read alphabetically everywhere and the workspace opens on the environment's config location, else the first by name; the workspace-repositories endpoint marks that one primary. Multi-repository workspaces opening at the root with a sidebar picker is a recorded TODO. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * docs(sandbox): the unordered create and the provision log line on the checklist Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * refactor(db): one migration for the branch; no provision log line The six incremental migrations sandbox-v2 accumulated (boot stamps added then dropped, position added then dropped, a cascade rebuilt) are regenerated as one, 0115_cloud_environments_repositories, before any of them reach main; a second generate finds no changes. The dev Neon branch's journal was rewritten to the single entry after its six recorded hashes matched the six old files and its schema matched the new migration. The provision job's structured log line is gone: the Sentry transaction is the record, and whether it arrives is checked after the production deploy. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * refactor(cloud): no environment hooks override, no stored checkout branch environments.hooks held one value anything read back: the internal environment's start hook. It is now the monorepo's own cloud-only `start` key in .superset/config.json, the release still runs internal-setup.sh itself, and the identity's SUPERSET_SANDBOX_HOOKS goes with the column. The release builds its golden from SUPERSET_INTERNAL_BRANCH (default main) so a branch can be released before it merges. cloud_workspace_repositories.branch only fed a box's first checkout and went stale after. The primary checks out the workspace's own branch and the rest their default branch, both read when the box is claimed. The branch migration is regenerated again as one file; the dev Neon branch had both columns dropped and its journal entry repointed. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * docs(sandbox): the start hook from config.json on the checklist Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * feat(cloud): a person's own GitHub connection; cloud workspaces commit, push and open PRs as them (#7555) * feat(cloud): a person's own GitHub connection; cloud workspaces commit, push and open PRs as them Settings → Connections gets a GitHub row. Connecting authorizes the GitHub App for the person (a user-to-server token): the API stores the tokens encrypted in github_user_connections (migration 0116, drizzle-kit output), refreshes them under a per-user lock since a refresh rotates the refresh token, and removes a connection GitHub no longer honours. Disconnecting deletes the row and revokes the grant. A cloud workspace's firewall rule for github.com and api.github.com now carries the creator's own token when they have connected, so pushes and pull requests are theirs and reach only what both they and the App can; the App's installation token otherwise. Create refuses an environment with a repository the connected person cannot see, instead of failing at clone. Commits on the box carry GIT_AUTHOR/COMMITTER from the connected account's no-reply address (GitHub attributes it and it passes "block pushes that expose my email"), else the Superset account's name and email. A running box now keeps its credential rules fresh: host-service asks the API every 30 minutes with its host secret, and the API re-applies the policy. Before, a box nobody reopened lost GitHub access when the hour-long installation token expired. GH_APP_CLIENT_ID and GH_APP_CLIENT_SECRET are optional; without them the row says the connection is unavailable and workspaces use the App's token. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(desktop): the GitHub connection row polls after Connect until it appears Returning from the browser to an Electron window never makes the page visible again, so the focus refetch never fired and the row stayed on "Not connected" until a reload. It now polls every two seconds from the Connect click until the connection shows up, for at most five minutes. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(sandbox): the release probe's credentials carry a git author Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * ci(sandbox): production release from CI; the image push logs itself in The Sandbox workflow gains a production-release dispatch (main only) that runs `bun run release --production` on a runner: image, internal golden, production environment rows. The image push mints a registry credential from VERCEL_SANDBOX_TOKEN and logs Docker in itself, the exchange `vercel vcr login` makes, because the CLI needs a user account and the project token has none. No 12-hour login to renew on a laptop either. Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP * fix(cloud): review fixes before main: personal environments, flag gating, provisioning and refresh races - Personal environments are enforced in the one loader every environment route uses (get, update, archive, secrets) and at workspace create; update no longer takes a sourceRef or bundleSha from clients, which could point an environment at any sandbox in the shared project; a config-repository-only update must name one of the environment's repositories. - Without the cloud workspaces flag the settings hid "Agent commands" instead of the cloud Agents page; the flag now filters sections and search counts through one helper. - A duplicate provision delivery deleted the first delivery's box; it now deletes only when the row was deleted. - A transient GitHub refresh failure removed the person's connection; only a rejected refresh token does now. - The start hook honors the config's cwd; malformed repository JSON no longer throws at boot. - The release resolves the monorepo before writing rows and writes the internal environment and its repositories in one transaction. - Considerations doc: a visitor to a workspace acts on GitHub as its creator (multiplayer by default). Claude-Session: https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP
Decisions from Satya's migration review of #7548.
One migration. The six incremental migrations
sandbox-v2accumulated (boot stamps added then dropped,positionadded then dropped, a cascade rebuilt) are regenerated as one,0115_cloud_environments_repositories, before any reachmain; a seconddrizzle-kit generatefinds no changes. The dev Neon branch's journal was rewritten to the single entry after its six recorded hashes matched the six old files and its schema matched the new migration (migrate is a no-op there). This PR's preview Neon branch, which had run the old six, was deleted so CI recreates it from production.Boot timing leaves the row. No stamp columns. The desktop's
cloud_workspace_openedevent, its create-start record, the terminal attach notifier and themeasurescript are removed (the P0 study is done; its numbers are in the start-time plan). The provision job is one Sentry transaction with a span per stage (claim, create, settle), sampled by transaction name so nothing else in the API is traced, flushed by the job since it runs after its request has answered. No fallback: whether it arrives is checked after the production deploy.No repository order. No
positioncolumn. Repositories read alphabetically everywhere; the workspace opens on the environment's config location, else the first by name;cloudWorkspace.repositoriesmarks that oneprimary. Opening multi-repository workspaces at/workspacewith a sidebar repository picker is a recorded TODO.No environment hooks override.
environments.hooksis dropped with the identity'sSUPERSET_SANDBOX_HOOKS: its only value read back was the internal environment'sstart, which is now the monorepo's cloud-only"start": ["superset-dev-stack"]in.superset/config.json; the release still runsinternal-setup.shitself and builds its golden fromSUPERSET_INTERNAL_BRANCH(defaultmain). A redesign ofconfig.jsonfor cloud is a recorded TODO.No stored checkout branch.
cloud_workspace_repositories.branchis dropped: it only fed a box's first checkout and went stale after. The primary checks out the workspace's own branch and the rest their default branch, both read when the box is claimed. Verified on a new two-repository box (identity lists both onmain, both checkouts onmain, no hooks key).Verified. Typecheck, lint, i18n (one changed string, 16 locales), sandbox lib and self-seed tests. On the dev stack: a single-repository create came up ready with the checkout at the root marked primary (stages claim 0.7 s, create 10.5 s, settle 1.3 s). Migrations review page: https://app.superset.sh/page/sandbox-v2-migrations-mhf14m
https://claude.ai/code/session_01FK6rLzBEUiHHXZWNmpQ2BP