Skip to content

feat(envd): add composite file upload API#2043

Merged
mishushakov merged 21 commits intomainfrom
mishushakov/riyadh-region
Mar 12, 2026
Merged

feat(envd): add composite file upload API#2043
mishushakov merged 21 commits intomainfrom
mishushakov/riyadh-region

Conversation

@mishushakov
Copy link
Member

@mishushakov mishushakov commented Mar 3, 2026

Summary

  • Replaces the earlier multi-part upload approach with a simpler composite upload API (POST /files/compose) that concatenates pre-uploaded part files into a single destination file using zero-copy kernel transfers (copy_file_range)
  • Adds gzip decompression support for file uploads/downloads, secure in-memory token storage via memguard, and comprehensive test coverage for encoding, download, multipart, and secure token handling
  • Includes OpenAPI spec updates (spec/envd.yaml) and generated code for the new endpoint

Test plan

  • Verify POST /files/compose correctly assembles parts into a single file and cleans up sources
  • Verify gzip-encoded uploads/downloads work end-to-end
  • Confirm existing POST /files upload path still works
  • Run make test in packages/envd/

🤖 Generated with Claude Code


Note

Medium Risk
Introduces a new file-writing endpoint that can delete source files, so bugs in path resolution/ownership or error handling could cause data loss or unexpected writes. Performance is generally good (zero-copy), but large compose requests still amplify disk I/O and require careful monitoring of ENOSPC and partial-failure cleanup.

Overview
Adds a new POST /files/compose endpoint (and updates OpenAPI + generated clients) that concatenates multiple existing files into a single destination using ReadFrom/copy_file_range, writing via a temp file + rename, creating parent dirs/setting ownership, and deleting sources on success; includes comprehensive unit tests for success and failure cases and bumps envd version to 0.5.5.

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

mishushakov and others added 14 commits March 2, 2026 17:28
…c writes

- Use sync.Map.LoadAndDelete to atomically claim upload sessions in
  Complete and Delete handlers, preventing concurrent requests from
  corrupting output files
- Replace lexicographic sort with numeric sort for part filenames to
  handle part numbers crossing the 6-digit boundary correctly
- Write assembled output to a temp file and rename on success to
  preserve any pre-existing file at the destination if assembly fails

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Check json.Encode return values and add blank lines before return
statements to satisfy errchkjson and nlreturn linters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a per-session RWMutex to prevent concurrent Complete/Delete from
snapshotting or removing the parts directory while a PUT is still writing.
PUTs hold an RLock; Complete/Delete acquire a write Lock that blocks until
all in-flight PUTs finish, eliminating silent data loss.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When file.ReadFrom(r.Body) fails after partial write, the part file is
left on disk with corrupt data. A subsequent Complete call would
silently assemble this truncated part into the final file. Close and
remove the part file in the error path to prevent this.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two concurrent Complete calls targeting the same destination path would
share the same tmpPath (derived only from meta.Path). The second caller
would truncate the first's in-progress assembly via O_TRUNC, corrupting
both. Include uploadId in the temp filename to make it unique.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When os.RemoveAll fails in the abort handler, the upload directory is
orphaned on disk but the session is already removed from the in-memory
map, so the files will never be cleaned up. Log the error and return
500 so the client knows the abort did not fully succeed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test checked for destPath + ".e2b-upload.tmp" which was never
created, so the assertion always passed trivially. Update to use the
actual temp file path that includes the uploadId.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
claimUpload atomically removes the session from the sync.Map and the
deferred os.RemoveAll unconditionally deletes all uploaded parts. If
assembly fails at any subsequent step, both the session metadata and
all part data are permanently destroyed — forcing the client to
re-upload every part from scratch.

On failure, re-register the session in the map and preserve the parts
directory so the client can retry Complete without re-uploading.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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: f5b2c9c503

ℹ️ 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".

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.

mishushakov and others added 3 commits March 3, 2026 16:27
CI runs tests as root, which bypasses DAC permission checks. The test
makes a directory read-only to trigger a compose failure, but root can
still write to it. Skip the test when running as root.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Return 400 if any resolved source path matches the destination to
prevent data loss from self-overwrite.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reject directories and device files early with 400 instead of letting
them fail later in ReadFrom with a 500.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mishushakov and others added 2 commits March 3, 2026 16:34
Replace ComposeResponse with EntryInfo in the compose endpoint response.
This aligns the response format with the upload endpoint.

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

@jakubno jakubno left a comment

Choose a reason for hiding this comment

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

Otherwise looks good

Copy link
Member

Choose a reason for hiding this comment

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

it should probably be compose.go

Copy link
Member

@jakubno jakubno left a comment

Choose a reason for hiding this comment

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

please bump envd version

@mishushakov mishushakov enabled auto-merge (squash) March 12, 2026 15:06
@mishushakov mishushakov merged commit d4d3293 into main Mar 12, 2026
35 checks passed
@mishushakov mishushakov deleted the mishushakov/riyadh-region branch March 12, 2026 15:18
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