feat(sdks): expose user-defined file metadata on sandbox.files#1383
feat(sdks): expose user-defined file metadata on sandbox.files#1383mishushakov wants to merge 5 commits into
Conversation
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>
PR SummaryMedium Risk Overview Uploads accept an optional Regenerated JS/Python protobuf and OpenAPI clients document xattr persistence ( Reviewed by Cursor Bugbot for commit 7049af8. Bugbot is set up for automated code reviews on this repo. Configure here. |
Package ArtifactsBuilt from f9a665e. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.27.2-mishushakov-files-metadata-sdk.0.tgzCLI ( npm install ./e2b-cli-2.10.4-mishushakov-files-metadata-sdk.0.tgzPython SDK ( pip install ./e2b-2.25.1+mishushakov.files.metadata.sdk-py3-none-any.whl |
🦋 Changeset detectedLatest commit: 7049af8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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>
…_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>
There was a problem hiding this comment.
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.
Reviewed by Cursor Bugbot for commit effe883. Configure here.
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>
| * 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> |
There was a problem hiding this comment.
🟡 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.
undefinedwhen 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):
- Write a file with
sandbox.files.write(filename, 'content')— nometadataoption, so noX-Metadata-*header is sent. Per the docstring,metadatashould remain undefined. - Resolve the absolute path with
realpath. - Run
setfattr -n user.e2b.author -v mish <path>viasandbox.commands.run— this sets auser.e2b.*xattr directly, bypassing the SDK upload entirely. - Call
sandbox.files.getInfo(filename)and assertinfo.metadatadeep-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.

Adds a
metadataoption to file uploads and surfaces persisted metadata on everyEntryInfo/WriteInforeturned bygetInfo,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 asuser.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
Python
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 asuser.e2b.*xattrs viasandbox.commands.runsurfacing ingetInfo. They require a sandbox running envd 0.6.2+.🤖 Generated with Claude Code