Machine providers: durable lifecycle and shared plugin APIs - #3274
Merged
Conversation
This was referenced Sep 8, 2026
ymichael
force-pushed
the
bb/machine-provider-apis
branch
7 times, most recently
from
September 11, 2026 17:26
ca79006 to
a440dec
Compare
Squash of bb/machine-provider-apis on top of 12e6e97. Machine creation is now the environment's first provisioning step, the machine migration is 0117 on top of the consolidation's 0116, and machine-phase progress and failures are reported into the thread transcript. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
focusPromptBox was a plain function declaration, so it got a new identity on every render. RootComposeView schedules it through requestAnimationFrame in an effect that lists it as a dependency, so calling it bumped focus-request state, re-rendered, produced a new identity, re-ran the effect, and called it again. The loop ran at frame cadence for as long as the composer was mounted. That effect early-returns unless startedComposing is true, which is why a cold load looked fine and returning to the new thread page from a thread did not: the loop only starts once composing has begun. Every frame it called focusEnd() on the prompt editor. Opening any menu put focus inside that menu, the next frame pulled focus back out, and Radix dismissed the layer -- the environment picker appeared to open and close instantly. Traced from an open-to-dismiss window of five milliseconds, with the dismissal stack running through our focusEnd into DismissableLayer's onDismiss. Memoize the callback so its identity is stable, and list it in the plugin composer host memo that reads it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
selectHosts silently returned every host, so ephemeral provider sandboxes leaked into machine pickers, the projectless machine chip, usage limits and the counts that gate multi-machine UI. It now takes a required scope and callers state whether they want persistent machines or all of them. The projectless chip keeps a selected ephemeral host in its list so filtering the choices never hides the current selection. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bb machine list, bb updates, and bb skill install-cli-skills enumerated every host, so disposable provider sandboxes appeared as ordinary machines and collected CLI installs and update runs they discard on recreation. The server default for CLI-skill status did the same. Each enumeration now selects persistent machines, matching the app's Show all machines reveal; bb machine list --all opts back in and the table gained a Type column so sandboxes are distinguishable. Lookups by id or name still resolve sandboxes, so --machine can still target one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ymichael
force-pushed
the
bb/machine-provider-apis
branch
from
September 11, 2026 20:12
2bd1ed9 to
92d25be
Compare
ymichael
added a commit
that referenced
this pull request
Sep 11, 2026
## Human comments ## What was wrong Connect machine cleanup could become permanently stuck after the remote revocation had already succeeded but its response was lost, or after local grant cleanup failed. On retry, the owned machine row was already revoked and no longer held a label claim, so `revokeMachine` returned `not-found` instead of acknowledging the completed revocation. The provider retained its local grant and every later retry failed the same way. ## What changed - Make revocation idempotent for an already-revoked machine owned by the authenticated account. - Preserve existing authentication and account-ownership checks: missing devices, foreign devices, bogus credentials, and revoked server credentials still fail. - Preserve label-claim tunnel closure and its retry behavior; a completed retry does not close the tunnel twice. - No UI, public API shape, CLI, configuration, or server/daemon wire contract changed. No protocol-version bump is needed. ## How you verified - Added a regression that fails with the previous 404 behavior and passes when a completed owned revocation is retried through both dashboard and server-credential paths. - `pnpm exec turbo run test --filter=@bb/web --force -- src/server/api.test.ts`: 31/31 passed. - `pnpm exec turbo run test --filter=bb-plugin-connect --force -- src/server-access.test.ts src/tunnel-lifecycle.test.ts`: 20/20 passed. - `pnpm exec turbo run typecheck build --filter=@bb/web --force`: passed. - `bb plugin build plugins/connect`: passed. - Targeted formatting and `git diff --check` passed. - Independently exercised response-loss, real SQLite deletion-failure, pending reconciliation, authorization, and socket-cleanup recovery cases with owned local fixtures; no cloud credentials or vendor resources were used. Follow-up to #3274. > AGENT GENERATED
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human comments
What was wrong
Cloud-backed workspaces need coordinated machine allocation, enrollment, server access, and durable lifecycle ownership. Without it, interrupted setup, pause/follow-up cycles, and project deletion can strand resources or interrupt other threads sharing a machine. Manual enrollment also read its command before preparation finished, and access readiness treated a registered provider as working access.
What changed
Adds experimental machine-provider and server-access APIs with UI, SDK, CLI, and Plugin Guide support. Core owns durable machine identity, encrypted bootstrap, enrollment, allocation checkpoints, checkout ownership, and coordinated lifecycle transitions. Plugins own vendor allocation, snapshots, and idle timing. Standalone machines have independent lifetimes; environment compositions pair machine and workspace providers through
register().Includes Manual enrollment, Connect server access, and Modal sandboxes. Modal supports configurable images and size presets, filesystem snapshots, same-host resume, and diagnostics. Its vendor implementation now lives under
providers/modal/, with a shared adapter for machine lifecycle registration. Idle policy, settings, diagnostics, and UI remain Modal-specific.Ephemeral machines account for live threads and launches across projects and environments, retiring after their final owner releases them. Durable
resumingstate holds queued follow-ups until restoration completes and drives settings, environment selection, and thread banners. Interrupted allocation, suspension, resume, and cleanup recover through persisted checkpoints. Checkout recovery retries when an earlier thread-start RPC settles, so a follow-up accepted after turn completion cannot remain stuck behind that RPC.Plugin bootstrap requires an executor and uses the selected access provider; Manual setup uses internal enrollment operations. Cancellation reaches enrollment and access acquisition. Cleanup uses the creation key before a checkpoint and the resource afterward. Both UI and CLI wait for manual command readiness. Access settings and composer banners consume validated availability and refresh on recheck and plugin enable/disable; errors and timeouts become unavailable without exposing private diagnostics.
Provider-icon registrations require an agent, machine, or environment kind in the SDK. Environment providers and compositions require a description and icon. Existing untyped plugins can omit these fields at runtime; omitted icon kinds apply across kinds, while a specific kind takes precedence. Cross-plugin icon overrides remain allowed. Older compositions that omit an icon use FolderUnknownIcon, without inheriting the machine provider logo. Mobile includes the same icon and its native SF Symbol mapping.
Default machine choices, CLI enumeration, updates, and CLI-skill installation exclude ephemeral sandboxes;
bb machine list --alland explicit machine targeting retain access to them. Active sandbox threads keep their current machine identity even with only one persistent machine. Project summaries count configured and total machines within the same scope; checkout counts follow the Show all machines toggle, and hidden online sandboxes do not mask offline persistent checkouts.Plugin Guide, API map, audit entries, and CLI guides describe the implemented contracts. Removed obsolete wording/feature assertions and added compiled examples and boundary regressions. Artifact tests copy nested plugin source directories so the Modal refactor is covered while retaining SDK bundling and Dockerfile assertions.
Migration
0117_machine_providers.sqlfollows main's0116. Host daemon protocol is 203, combining main's protocol changes with this branch's machine/bootstrap/shutdown changes. Plugin SDK version is 0.4.83. Modal participates in the bundled-plugin preparation graph.How you verified
Rebased onto
bc75f9dde7and rebuilt the source app, server, daemon, bundled plugins, and matching CLI. Local checks passed 9,732 tests, with five skips, across app, server, daemon, CLI, database, domain, Plugin SDK, API map, server/daemon contracts, scripts, Connect, Modal, and plugin-build. Relevant typechecks and lint passed.The artifact regression reproduced locally before the fixture fix and passes afterward. The previous Linux package-smoke failure occurred during Ubuntu mirror installation; the rebase includes main's mirror fix.
Fresh live QA uses this worktree's isolated source server and built CLI, real Modal compute/snapshots, Connect access, and Pi agent turns executing shell tools:
The natural idle sweep suspended a third sandbox after 76 seconds with a temporary one-minute threshold. A follow-up restored its files and ran once; archive while paused automatically destroyed it in one attempt. The original ten-minute setting was restored. Independent Modal API checks confirm all three allocations have no compute and all seven captured snapshots were deleted. All six pre-existing machine phases and general/access/Modal settings match before and after.
All applicable CI checks passed on
30bf86c10b, including workspace build/typecheck/lint, every test shard, Linux/macOS package smoke, bundle budgets, and SDK/version guards. iOS and optional Node compatibility smoke are skipped by workflow conditions. The subsequent picker-description removal passes all 31 picker tests. Direct folder-browser loading is restored; omitted composition icons use FolderUnknownIcon. The affected app/server tests (18 tests), typechecks, lint, and bundle budget pass. The measured boot payload is 1,610.0 KB raw / 394.6 KB brotli, and the thread-route closure is 2,142.9 KB raw / 571.3 KB brotli. The latest CI fixes pass all 82 integration tests, all 334 mobile tests, 18 server provisioning tests, and relevant typechecks/lint. A controlled delayed start acknowledgment reproduces the checkout recovery failure before the fix and verifies successful follow-up output afterward. Integration tests ran locally with two workers after a fully parallel run encountered startup timeouts during concurrent builds. All applicable CI checks pass on5c38bd6fb4, including the integration and packages shards, workspace checks, all app/server tests, and Linux/macOS package smoke; GitHub reports the PR mergeable.Earlier passes covered stopped/interrupted setup, scheduled work and suspension recovery across server restart, access-provider errors/timeouts, and live access banner refresh. This pass does not claim desktop/mobile QA or installation of a separately managed Manual daemon. Removal of an independently managed unreachable machine can still require workspace cleanup; automatic abandonment is not implemented.
Pre-merge CLI/server rerun on
5c38bd6fb4passed with real Modal, Connect, Pi and Codex: two projects/eight environments/ten threads across one and two sandboxes; ten simultaneous offline follow-ups with exact single writes; stop/follow-up; cross-project deletion; shared-owner cleanup while the other host runs; active-turn suspension; and separate Codex and natural-idle resume checks. Manual command readiness and idempotent creation also passed. Auxiliary fixture collisions were corrected and rerun. All five allocations created during the run were automatically destroyed in one attempt, and independent Modal queries confirmed no compute and all eight captured snapshots deleted. Settings and all seven pre-existing machine phases match the baseline. Cleanup emitted non-blocking Keep Awake/offline and Codex archive/machine-removing warnings; final archive and cleanup succeeded. No code changes were needed for that QA pass.The display fixes in
2bd1ed9f36add three regression tests for current sandbox identity, scoped project counts/offline status, and checkout counts across the Show all machines toggle. All three fail before the fixes and pass afterward. All 66 focused app tests pass; app typecheck passes and lint reports no errors. The real-provider QA above ran on5c38bd6fb4, before the subsequent machine-enumeration and display changes; That push exposed the upstream Codex parity fixture failure described below.Rebased onto
18b71d810cto include #3526's fix for Codex warning parity. The earlier CI packages failure came from #3478 changing warning classification while the recorded expected event still used the unhandled-event fallback. #3526 refreshes that single fixture and pins its unhandled count to zero; it also adds provider source files to the parity test's Turbo inputs. All 21 branch commits replayed without conflicts. On rebased head92d25beb50, all 56 provider-parity tests, 281 Codex provider tests, and 66 focused app tests pass, along with app/parity/Codex typechecks. All applicable checks pass on rebased head92d25beb50in CI run 34643021790, including packages, server, all app shards, integration, workspace build/typecheck/lint, and Linux/macOS package smoke. iOS and optional Node compatibility jobs are skipped by workflow conditions.