Skip to content

feat: add Volume CRUD operations to SDKs#1126

Open
mishushakov wants to merge 67 commits intomainfrom
mishushakov/volume-crud-sdk
Open

feat: add Volume CRUD operations to SDKs#1126
mishushakov wants to merge 67 commits intomainfrom
mishushakov/volume-crud-sdk

Conversation

@mishushakov
Copy link
Member

@mishushakov mishushakov commented Feb 10, 2026

Summary

Add Volume CRUD operations to both TypeScript and Python SDKs. Volumes are persistent storage that can be mounted to sandboxes. Includes create, list, get_info, and destroy methods. Sandbox creation now supports volumeMounts parameter.

  • Updated OpenAPI spec with volume endpoints and schemas
  • Generated API clients for both SDKs using codegen
  • Implemented Volume class in TypeScript with static methods
  • Implemented sync/async Volume classes in Python with same API
  • Added volumeMounts support to Sandbox creation flow
  • Added volume tag to codegen filters for spec filtering

Usage Examples

TypeScript

import { Volume, Sandbox } from 'e2b'

// Create a volume
const volume = await Volume.create('my-data')
console.log(volume.volumeId, volume.name)

// List all volumes
const volumes = await Volume.list()

// Get volume info
const info = await Volume.getInfo(volume.volumeId)

// File operations on a volume
await volume.writeFile('/data/hello.txt', 'Hello, World!')
const content = await volume.readFile('/data/hello.txt')

// Directory operations
await volume.makeDir('/data/subdir', { force: true })
const entries = await volume.list('/data')

// Check if a path exists
const exists = await volume.exists('/data/hello.txt')

// Get file/directory info
const stat = await volume.getInfo('/data/hello.txt')

// Remove files
await volume.remove('/data/hello.txt')
await volume.remove('/data/subdir', { recursive: true })

// Mount a volume to a sandbox
const sandbox = await Sandbox.create('base', {
  volumeMounts: {
    '/mnt/data': volume,       // mount by Volume instance
    '/mnt/other': 'other-vol', // mount by volume name
  },
})

// Connect to an existing volume
const existing = await Volume.connect(volume.volumeId)

// Destroy a volume
await Volume.destroy(volume.volumeId)

Python (async)

from e2b import AsyncSandbox, AsyncVolume

# Create a volume
volume = await AsyncVolume.create("my-data")
print(volume.volume_id, volume.name)

# List all volumes
volumes = await AsyncVolume.list()

# Get volume info
info = await AsyncVolume.get_info(volume.volume_id)

# File operations on a volume
await volume.write_file("/data/hello.txt", "Hello, World!")
content = await volume.read_file("/data/hello.txt")

# Directory operations
await volume.make_dir("/data/subdir", force=True)
entries = await volume.list("/data")

# Check if a path exists
exists = await volume.exists("/data/hello.txt")

# Get file/directory info
stat = await volume.get_info("/data/hello.txt")

# Remove files
await volume.remove("/data/hello.txt")
await volume.remove("/data/subdir", recursive=True)

# Mount a volume to a sandbox
sandbox = await AsyncSandbox.create(
    "base",
    volume_mounts={
        "/mnt/data": volume,       # mount by Volume instance
        "/mnt/other": "other-vol", # mount by volume name
    },
)

# Connect to an existing volume
existing = await AsyncVolume.connect(volume.volume_id)

# Destroy a volume
await AsyncVolume.destroy(volume.volume_id)

Python (sync)

from e2b import Sandbox, Volume

# Create a volume
volume = Volume.create("my-data")
print(volume.volume_id, volume.name)

# List all volumes
volumes = Volume.list()

# Get volume info
info = Volume.get_info(volume.volume_id)

# File operations on a volume
volume.write_file("/data/hello.txt", "Hello, World!")
content = volume.read_file("/data/hello.txt")

# Directory operations
volume.make_dir("/data/subdir", force=True)
entries = volume.list("/data")

# Check if a path exists
exists = volume.exists("/data/hello.txt")

# Get file/directory info
stat = volume.get_info("/data/hello.txt")

# Remove files
volume.remove("/data/hello.txt")
volume.remove("/data/subdir", recursive=True)

# Mount a volume to a sandbox
sandbox = Sandbox.create(
    "base",
    volume_mounts={
        "/mnt/data": volume,       # mount by Volume instance
        "/mnt/other": "other-vol", # mount by volume name
    },
)

# Connect to an existing volume
existing = Volume.connect(volume.volume_id)

# Destroy a volume
Volume.destroy(volume.volume_id)

Testing

  • TypeScript build passes with no type errors
  • Python imports work and classes are instantiable
  • Both SDKs pass format and lint checks
  • Generated API client models use correct field mappings (id/name)

Note

Medium Risk
Adds a new persistent storage surface (volume CRUD + file operations) and threads volumeMounts through sandbox creation, which could break clients if request/response shapes or auth tokens are mishandled. Most changes are additive and codegen-driven, but they touch API wiring and runtime network calls.

Overview
Adds first-class Volumes support across SDKs. The JS SDK now exposes a Volume API (CRUD plus file/dir operations via a separate volume-content OpenAPI client) and exports the new types, errors, and client config.

Sandbox creation gains volume mounts. Sandbox.create (TS) and Sandbox/AsyncSandbox.create (Python) accept volumeMounts/volume_mounts and transform mount maps into the API volumeMounts payload; sandbox info parsing now surfaces returned mounts.

Spec/codegen updates and tests. OpenAPI-generated schemas add /volumes endpoints and related models (e.g., VolumeAndToken), log querying gains level/search filters, and new JS tests cover volume CRUD and file operations; Python generation scripts add a separate generate-volume-api step and exports new Volume/AsyncVolume entrypoints.

Written by Cursor Bugbot for commit fbc8402. This will update automatically on new commits. Configure here.

@changeset-bot
Copy link

changeset-bot bot commented Feb 10, 2026

🦋 Changeset detected

Latest commit: fbc8402

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@e2b/python-sdk Minor
e2b Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@mishushakov
Copy link
Member Author

@djeebus no need to review, it's a draft

@mishushakov mishushakov force-pushed the mishushakov/volume-crud-sdk branch from 019d887 to e11378c Compare February 10, 2026 21:19
@mishushakov mishushakov reopened this Feb 10, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 18, 2026

Package Artifacts

Built from cd1df8e. Download artifacts from this workflow run.

JS SDK (e2b@2.14.2-mishushakov-volume-crud-sdk.0):

npm install ./e2b-2.14.2-mishushakov-volume-crud-sdk.0.tgz

CLI (@e2b/cli@2.8.1-mishushakov-volume-crud-sdk.0):

npm install ./e2b-cli-2.8.1-mishushakov-volume-crud-sdk.0.tgz

Python SDK (e2b==2.15.1+mishushakov-volume-crud-sdk):

pip install ./e2b-2.15.1+mishushakov.volume.crud.sdk-py3-none-any.whl

@mishushakov mishushakov marked this pull request as ready for review February 19, 2026 14:14
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ae99f53cf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@mishushakov mishushakov force-pushed the mishushakov/volume-crud-sdk branch from 4cd8756 to 2632556 Compare March 6, 2026 15:32
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

if (!(err instanceof NotFoundError)) {
throw err
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Volume remove makes redundant API call on every invocation

Medium Severity

The remove method always makes an extra getInfo/get_info API call before every delete operation to determine if the path is a file or directory (to choose between DELETE /file and DELETE /dir). This doubles the number of API requests for every remove operation. For non-existent paths, this results in two failed requests: getInfo returns 404 (caught and swallowed), then DELETE /file also returns 404 (thrown). There's also a TOCTOU race condition: the entry type could change between the getInfo check and the DELETE call, causing the wrong endpoint to be used.

Additional Locations (1)

Fix in Cursor Fix in Web

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