Skip to content

feat(sdks): expose user-defined file metadata on sandbox.files#1383

Open
mishushakov wants to merge 5 commits into
mainfrom
mishushakov/files-metadata-sdk
Open

feat(sdks): expose user-defined file metadata on sandbox.files#1383
mishushakov wants to merge 5 commits into
mainfrom
mishushakov/files-metadata-sdk

Conversation

@mishushakov
Copy link
Copy Markdown
Member

@mishushakov mishushakov commented Jun 4, 2026

Adds a metadata option to file uploads and surfaces persisted metadata on every EntryInfo / WriteInfo returned by getInfo, list, rename, and write responses, across the JS and Python (sync + async) SDKs.

Metadata is sent as X-Metadata-<key>: <value> request headers and persisted by envd as user.e2b.* extended attributes; the same map is applied to every file in a multi-file upload. Keys and values must be printable US-ASCII and keys are lowercased by the sandbox, so they may differ in case when read back. Requires envd 0.6.2 or later.

This syncs the envd OpenAPI spec and filesystem proto with infra#2732 and regenerates the JS/Python clients.

Usage

JavaScript / TypeScript

// Single file
const info = await sandbox.files.write('report.txt', 'hello', {
  metadata: { author: 'mish', purpose: 'demo' },
})
console.log(info.metadata) // { author: 'mish', purpose: 'demo' }

// Multiple files (same metadata applied to each)
await sandbox.files.writeFiles(
  [
    { path: 'a.txt', data: 'A' },
    { path: 'b.txt', data: 'B' },
  ],
  { metadata: { source: 'import' } }
)

// Read it back
const stat = await sandbox.files.getInfo('report.txt')
console.log(stat.metadata) // { author: 'mish', purpose: 'demo' }

Python

# Single file
info = sandbox.files.write("report.txt", "hello", metadata={"author": "mish"})
print(info.metadata)  # {"author": "mish"}

# Multiple files (same metadata applied to each)
sandbox.files.write_files(
    [
        WriteEntry(path="a.txt", data="A"),
        WriteEntry(path="b.txt", data="B"),
    ],
    metadata={"source": "import"},
)

# Read it back
stat = sandbox.files.get_info("report.txt")
print(stat.metadata)  # {"author": "mish"}

The async Python API is identical with await.

Tests

Integration tests cover the round-trip across write / getInfo / list / rename, octet-stream uploads, multi-file uploads, overwrite-clears-stale-metadata, and metadata written directly as user.e2b.* xattrs via sandbox.commands.run surfacing in getInfo. They require a sandbox running envd 0.6.2+.

🤖 Generated with Claude Code

Adds a metadata option to file uploads and surfaces persisted metadata
on EntryInfo/WriteInfo returned by getInfo, list, rename, and write.
Metadata is sent as X-Metadata-<key> headers and persisted by envd as
user.e2b.* xattrs. Syncs the envd OpenAPI spec and filesystem proto,
regenerates the JS and Python clients, and adds integration tests.

Requires envd 0.5.26 or later.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cla-bot cla-bot Bot added the cla-signed label Jun 4, 2026
@cursor
Copy link
Copy Markdown

cursor Bot commented Jun 4, 2026

PR Summary

Medium Risk
Touches public filesystem APIs and upload headers across JS/Python with an envd version dependency; behavior is well-tested but wrong envd/template versions will fail at runtime.

Overview
Adds user-defined file metadata to sandbox.files in the JS and Python (sync + async) SDKs, aligned with envd 0.6.2+ and updated OpenAPI/proto specs.

Uploads accept an optional metadata map on write / writeFiles / write_files. The SDKs send it as X-Metadata-<key> headers (same map for every file in a multi-file upload) and gate usage with ENVD_FILE_METADATA / TemplateError when envd is too old. WriteInfo and EntryInfo gain an optional metadata field, populated from upload responses and from RPC paths (getInfo, list, rename) via a new EntryInfo.metadata map in the filesystem proto.

Regenerated JS/Python protobuf and OpenAPI clients document xattr persistence (user.e2b.*), replace-on-upload semantics, and ASCII/size limits. Integration tests cover round-trip writes, octet-stream uploads, multi-file uploads, list/rename, overwrite clearing metadata, and xattrs surfaced through getInfo.

Reviewed by Cursor Bugbot for commit 7049af8. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 4, 2026

Package Artifacts

Built from f9a665e. Download artifacts from this workflow run.

JS SDK (e2b@2.27.2-mishushakov-files-metadata-sdk.0):

npm install ./e2b-2.27.2-mishushakov-files-metadata-sdk.0.tgz

CLI (@e2b/cli@2.10.4-mishushakov-files-metadata-sdk.0):

npm install ./e2b-cli-2.10.4-mishushakov-files-metadata-sdk.0.tgz

Python SDK (e2b==2.25.1+mishushakov-files-metadata-sdk):

pip install ./e2b-2.25.1+mishushakov.files.metadata.sdk-py3-none-any.whl

Comment thread packages/js-sdk/src/sandbox/filesystem/index.ts Outdated
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 4, 2026

🦋 Changeset detected

Latest commit: 7049af8

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

This PR includes changesets to release 2 packages
Name Type
e2b Minor
@e2b/python-sdk 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

….6.2

Re-syncs the envd OpenAPI spec and filesystem proto with the latest
infra#2732 (refined upload description and user.e2b. xattr namespace
note), regenerates the JS client, and pins the file-metadata version
gate to envd 0.6.2 (the version that ships the feature).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Comment thread packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py Outdated
Comment thread packages/python-sdk/e2b/sandbox_async/filesystem/filesystem.py Outdated
Comment thread packages/js-sdk/tests/sandbox/files/metadata.test.ts
Comment thread packages/js-sdk/tests/sandbox/files/metadata.test.ts
…_dict

- Move file metadata to WriteEntry so each file in a multi-file write can
  carry its own; keep the metadata option on single-file write/write.
- Upload per file when metadata is set (X-Metadata-* headers are
  request-scoped), preserving the batched multipart path otherwise.
- Replace the module-level _write_info_from_dict helper with a
  WriteInfo.from_dict() classmethod.
- Trim the version-gate error message to a single sentence.
- Add tests that set metadata via xattrs (user.e2b.*) through
  sandbox.commands.run and assert it surfaces in getInfo (JS + Python).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copy link
Copy Markdown

@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.

Fix All in Cursor

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

Reviewed by Cursor Bugbot for commit effe883. Configure here.

Comment thread packages/js-sdk/src/sandbox/filesystem/index.ts Outdated
Revert per-WriteEntry metadata; metadata is again an option on
write/writeFiles/write_files and is applied to every file in the upload
(sent as request-scoped X-Metadata-* headers). Keeps the earlier review
fixes: WriteInfo.from_dict(), the trimmed version-gate message, and the
xattr-via-commands tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mishushakov mishushakov marked this pull request as ready for review June 4, 2026 15:12
Comment on lines 78 to +86
* Path to the filesystem object.
*/
path: string
/**
* User-defined metadata persisted on the file as extended attributes.
* Only populated when metadata was supplied on upload and the sandbox's
* envd supports it. `undefined` when no metadata is set.
*/
metadata?: Record<string, string>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 The docstring on WriteInfo.metadata says it is "Only populated when metadata was supplied on upload and the sandbox's envd supports it." Since EntryInfo extends WriteInfo and inherits this doc, it also describes EntryInfo.metadata returned by getInfo/list/rename — which is populated from any user.e2b.* xattr on the inode, including ones set out-of-band (as the PR's own "metadata set via xattrs is surfaced in getInfo" test demonstrates). Consider dropping the "Only" qualifier or noting that read responses also reflect xattrs set externally. Same wording is duplicated in packages/python-sdk/e2b/sandbox/filesystem/filesystem.py:52-57.

Extended reasoning...

What the docstring says vs. what actually happens

The new metadata field on WriteInfo is documented as:

User-defined metadata persisted on the file as extended attributes. Only populated when metadata was supplied on upload and the sandbox's envd supports it. undefined when no metadata is set.

(packages/js-sdk/src/sandbox/filesystem/index.ts:81-85, mirrored verbatim in packages/python-sdk/e2b/sandbox/filesystem/filesystem.py:52-57.)

The word "Only" makes that statement incorrect because EntryInfo extends WriteInfo (index.ts:89) and inherits this docstring. EntryInfo is returned by Filesystem.getInfo, Filesystem.list, and Filesystem.rename, all of which call into envd RPCs (stat, listDir, move) that surface the proto EntryInfo.metadata field. The proto comment on filesystem.proto:65-68 is the authoritative description and correctly says the field reflects any user.e2b.* xattr on the inode — regardless of whether the SDK set it on upload.

Step-by-step proof from the PR's own test

The PR adds packages/js-sdk/tests/sandbox/files/metadata.test.ts:155 (metadata set via xattrs is surfaced in getInfo):

  1. Write a file with sandbox.files.write(filename, 'content') — no metadata option, so no X-Metadata-* header is sent. Per the docstring, metadata should remain undefined.
  2. Resolve the absolute path with realpath.
  3. Run setfattr -n user.e2b.author -v mish <path> via sandbox.commands.run — this sets a user.e2b.* xattr directly, bypassing the SDK upload entirely.
  4. Call sandbox.files.getInfo(filename) and assert info.metadata deep-equals { author: 'mish' }.

The assertion passes, which means EntryInfo.metadata (the inherited field) is populated from xattrs that were never "supplied on upload". The same scenario is repeated in the Python test suites (packages/python-sdk/tests/{async,sync}/.../test_metadata.py:test_metadata_set_via_xattrs_surfaced_in_get_info). The docstring's "Only" is therefore factually wrong for the EntryInfo use site.

Why this is still only a nit

The refuters are correct that this is purely a documentation cleanup, not a behavior bug — the runtime is right, the OpenAPI description block (envd.yaml:104-124) and the proto comment both document the xattr semantics properly, and any developer looking at EntryInfo.metadata in an IDE would see the cross-cutting proto docs alongside the inherited TS/Python comment. So this should not block the PR.

Suggested fix

Either drop the "Only" qualifier and mention reads, e.g.:

User-defined metadata persisted on the file as extended attributes
(`user.e2b.*` xattrs). On uploads, populated when metadata was supplied
and the sandbox's envd supports it. On reads (`getInfo`/`list`/`rename`),
populated from any `user.e2b.*` xattr on the inode, including ones set
out-of-band. `undefined`/`None` when no such metadata is set.

Apply to both packages/js-sdk/src/sandbox/filesystem/index.ts:78-86 and packages/python-sdk/e2b/sandbox/filesystem/filesystem.py:52-57 so the JS/Python copies stay in sync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant