Repository navigation
feat(e2b): support mounting pre-existing volumes at sandbox creation - #3581
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
@jujn @Buktal Could you help review this PR? Suggested focus areas:
|
|
我先看完这个[捂脸]:#3562 |
Buktal
left a comment
There was a problem hiding this comment.
E2bSandboxState gains volumeMounts/workspaceOnVolume but neither the class nor the client's mapper sets @JsonIgnoreProperties(ignoreUnknown = true) (E2bSandboxState.java, E2bSandboxClient.java:48-51 — Jackson's FAIL_ON_UNKNOWN_PROPERTIES is on by default). Once a state written with mounts is loaded by an older build, deserializeState throws and SandboxManager falls through to a fresh create (SandboxManager.java:122-129), losing session continuity for those scopes. The sandbox snapshot classes already carry this annotation (LocalSandboxSnapshot.java:42) — please add it to E2bSandboxState or disable the failure on the mapper.
WorkspaceMountSupport.tarExcludeArgsForAbsolutePaths misses workspaceRoot = "/": root + "/" becomes "//" so mount.startsWith(root + "/") never matches and no nested mount is excluded (WorkspaceMountSupport.java:105) — tar -C / . then archives the entire volume. Please guard the root-"/" case (cf. the special-case in E2bSandbox.workspaceTransferPath) and collapse duplicate slashes, which currently also skip exclusion while E2bVolumeMount.normalizeMountPath normalizes them.
Removing the published mounts/E2bCloudBucketMountStrategy interface is a binary/source break for downstream consumers even though nothing in this repo references it. Please either keep it @Deprecated for a transition release, or flag the removal as a breaking change for maintainer sign-off — the contribution rules ask to preserve public interfaces.
E2bSandboxState.setVolumeMounts skips the distinct-path validation that E2bSandboxClientOptions.setVolumeMounts enforces, so deserialized or directly-set state can send duplicate mount paths to the platform. Please mirror the validation (or centralize it in E2bVolumeMount).
The volume-critical branches — persist no-op, empty-stream hydrate return, destroy skip (E2bSandbox.java) — are only exercised by the E2B_API_KEY-gated live test and show as uncovered in codecov (48% on E2bSandbox). They are unit-testable with MockWebServer plus the reflection pattern already used in E2bVolumeMountTest.recreateCarriesMounts; please add unit tests so CI can catch regressions.
Minor:
- The "Returns whether the workspace root lives on a volume" javadoc is attached to
requireAbsoluteWorkspaceRoot(E2bSandboxClient.java:174-179) but describesisWorkspaceOnVolume. resume()skips therequireAbsoluteWorkspaceRootcheck enforced oncreate(), so a persisted relative root still slips through.E2bVolumeMountLiveTest.volumeName()falls back to a hardcodedprobe-ws-test1— other accounts get an opaque 400; please requireE2B_VOLUME_NAME.- The docs section should note volumes are read-write and multi-mountable with no locking guarantees (E2B beta limitation), which matters for the workspace-on-volume example it encourages.
- Stray double blank lines at the end of the new section in
docs/v2/en/docs/harness/filesystem.mdand the zh counterpart.
Sequencing note: #3562 reworks the same ensureSandbox/restoreSandboxFromSnapshotTemplate paths without mounts; whichever merges second must thread e2bState.getVolumeMounts() through the new helpers, or NATIVE_SNAPSHOT + volume sessions silently lose their mounts on restore.
|
Addressed all review feedback in 6b0b53e (all verified: 115 unit + 6 live green, spotless clean, no leftover resources):
Note: #3562 is still open — if it merges first, I will rebase and thread mounts through the reworked helpers. |
2f10589 to
3cca0f5
Compare
|
CI is green again and the branch is rebased onto latest
All checks pass now — build green on both ubuntu and windows. @Buktal ready for re-review when you get a chance. |
3cca0f5 to
0ff259e
Compare
Buktal
left a comment
There was a problem hiding this comment.
buildTarCommand applies the new volume-mount excludes only on the create branch (E2bSandbox.java:587-591), while the pre-existing bind-mount excludes are appended for both create and extract (E2bSandbox.java:582-586). An archive that predates the current mount configuration — e.g. a NATIVE_SNAPSHOT template created before a volume was mounted under the workspace root — therefore extracts its old ./data/... entries straight through the mount point into the persistent volume on hydrate. Please apply volumeTarExcludeArgs() on the -xf branch as well, matching the bind-mount handling in the same method.
This PR adds a second near-copy of path normalization: WorkspaceMountSupport.normalizeAbsolutePath/stripTrailingSlash (WorkspaceMountSupport.java:120-132) duplicates E2bVolumeMount.normalizeMountPath (E2bVolumeMount.java:94-108 — identical collapse/strip logic, different error contract), next to the third variant in E2bSandbox.workspaceTransferPath (E2bSandbox.java:619-623). The duplication already bit this PR once — the collapse/strip ordering bug fixed after review lived in exactly one of the copies. Since the e2b module already calls into WorkspaceMountSupport (E2bSandbox.java:551), please consolidate on one shared core instead of maintaining parallel copies that can drift again.
Minor:
- Quoting the bind-mount excludes (
E2bSandbox.java:584-585) changes the generated command for existing bind-mount users, but only the volume branch is locked by a test — please mirrortarCommandQuotesExcludeArgsfor the bind-mount excludes. doDestroyWorkspaceguards only theworkspaceOnVolumecase (E2bSandbox.java:243-246); with a nested mount,rm -rfon the root would traverse the mount point into the volume. Nothing calls it in production today (real teardown iskillSandbox), so it is latent — but if the "on shutdown" contract ofAbstractBaseSandbox(AbstractBaseSandbox.java:45) gets wired, this needs to exclude nested mounts the way the TAR path does.- Consider
@JsonIgnoreProperties(ignoreUnknown = true)onE2bVolumeMounttoo: the annotation onE2bSandboxStatedoes not cover nested types, so a future field inside a mount entry would fail deserialization of the whole state — the same failure mode just fixed at the state level. - The 0-byte archive doubles as an implicit no-op marker: a workspace that flips between volume and non-volume persistence across runs can have a real archive replaced by a 0-byte marker (or hydrate silently skip one). If intended, a one-line comment stating the marker semantics would help.
- Add E2bVolumeMount POJO (existing volume name + absolute in-sandbox
path, with validation and normalization)
- Add volumeMounts to E2bSandboxClientOptions (fluent adders, distinct
path check) and E2bFilesystemSpec.volumeMount/volumeMounts
- Serialize volumeMounts [{name, path}] into POST /sandboxes (omitted
when empty); platform 400s propagate with the response body
- Thread mounts through E2bSandboxClient.create into E2bSandboxState
and across all creation paths (new, connect-fallback recreate,
native-snapshot restore)
- Track workspaceOnVolume (root.equals(mount) ||
root.startsWith(mount + "/")); TAR persist/hydrate become no-ops and
destroy skips rm -rf when true; nested mounts are excluded from TAR
archives via WorkspaceMountSupport.tarExcludeArgsForAbsolutePaths
- Fail fast on non-absolute workspaceRoot in create(); shell-quote TAR
--exclude args; run workspace mkdir from / so volume subdirectories
can be created on first start
- Keep E2bSandboxState JSON backward compatible; remove the
E2bCloudBucketMountStrategy placeholder
- Cover with unit tests (request body, validation, merge, serde,
recovery) and document volumes in filesystem.md (EN/ZH)
Refs: agentscope-ai#3155
Opt-in suite (runs only with E2B_API_KEY set; skipped in CI): mount visibility, unknown-volume 400, cross-sandbox persistence, workspaceOnVolume end-to-end, native-snapshot restore, and nested mount independence from snapshots. Volume name via E2B_VOLUME_NAME. Verified live: platform snapshots do not capture volume bytes. Refs: agentscope-ai#3155
- Keep E2bSandboxState forward compatible with @JsonIgnoreProperties(ignoreUnknown = true) so states written with mounts load on older builds instead of falling back to fresh create - Fix tarExcludeArgsForAbsolutePaths for workspaceRoot "/" and collapse duplicate slashes, matching E2bVolumeMount normalization - Keep E2bCloudBucketMountStrategy as @deprecated for one transition release instead of removing the public interface outright - Centralize distinct-mount-path validation in E2bVolumeMount and enforce it in E2bSandboxState.setVolumeMounts as well - Add unit tests for the volume-critical branches (persist no-op, empty hydrate, destroy skip), "/" excludes, state validation and unknown-field serde so CI catches regressions - Fix misplaced javadoc on requireAbsoluteWorkspaceRoot and enforce the absolute-root check in resume() too - Require E2B_VOLUME_NAME for live tests instead of a hardcoded default volume - Document read-write/multi-mount/no-locking volume semantics Refs: agentscope-ai#3155
0ff259e to
5f6b677
Compare
Preview approval requiredThis pull request comes from a fork, so Mintlify won't build a preview until someone with write access approves commit |
|
Addressed this second round in Mandatory 1 — volume excludes missing on Mandatory 2 — three normalization copies: consolidated on one shared core, Minor 1: added Minor 2: make Minor 3: Minor 4: the empty-archive marker semantics are now documented in both Verification: full harness suite (1257), e2b mock suites (145), and all 6 live tests (including snapshot restore under the new |
Buktal
left a comment
There was a problem hiding this comment.
Verified 5f6b677 end to end: volume excludes now apply on both the -cf and -xf branches with the quoting test extended to the extract form; normalization is consolidated into WorkspaceMountSupport.normalizeVirtualPath with E2bVolumeMount.normalizeMountPath and workspaceTransferPath delegating to it, plus a direct unit test; @JsonIgnoreProperties on E2bVolumeMount and the empty-archive marker docs are in place; the new tarCommandQuotesBindMountExcludeArgs locks quoting on both branches. Ran the harness and e2b suites locally on this head (153 e2b tests, 0 failures, 9 live-gated skips; harness green) on top of the green dual-OS CI. The doDestroyWorkspace no-op is the right call for this backend — the disk is bounded by the sandbox lifetime, so a pre-teardown delete has no observable effect and could only reach through a nested mount into shared volume data; the override documents exactly that.
Closes #3155.
E2B offers persistent volumes that exist independently of sandbox lifecycles. This PR wires attach-at-create through the E2B backend so users no longer need TAR persist/hydrate on every turn for cross-sandbox persistence.
What is included
E2bVolumeMountPOJO (existing volume name + absolute in-sandbox path, validation + normalization)E2bSandboxClientOptions.volumeMounts(fluent adders, distinct-path check) andE2bFilesystemSpec.volumeMount/volumeMountsPOST /sandboxesserializesvolumeMounts: [{name, path}](omitted when empty); platform 400s propagate with the response bodyE2bSandboxClient.createintoE2bSandboxStateand across all three creation paths (new, connect-fallback recreate, native-snapshot restore)workspaceOnVolumedetection (root.equals(mount) || root.startsWith(mount + "/")): TAR persist/hydrate become no-ops, destroy skipsrm -rf; nested mounts get TAR--excludeargsworkspaceRoot, shell-quote TAR excludes, run workspacemkdirfrom/E2bSandboxStateJSON stays backward compatible; emptyE2bCloudBucketMountStrategyplaceholder removedfilesystem.md(EN/ZH)Tests
E2bVolumeMountLiveTest, opt-in viaE2B_API_KEY, skipped in CI): mount visibility, unknown-volume 400, cross-sandbox persistence,workspaceOnVolumeend-to-end, native-snapshot restore, nested-mount independence from snapshots — all passing against the real API