Hive
fix(server): route build multipart upload start to the project’s account
GitHub issue · Closed
Describe here the purpose of your PR.
What
multipart_start for build artifact uploads now selects the storage backend from selected_project.account instead of Authentication.authenticated_subject_account(conn), making it consistent with multipart_generate_url, multipart_complete, and the runs/analytics controller.
Why (root cause)
A customer’s tuist test run created the build run successfully, but the xcactivitylog (build.zip) never uploaded, so server-side build processing never ran. Every part PUT to S3 failed with:
404 NoSuchUpload — The specified upload does not exist.
UploadId: 592c5bdd-8760-3721-c345-734875f2d0a2
After 3 retries the CLI gave up and never called /builds/upload/complete. The test-run result_bundle in the same session uploaded fine.
The tell was the upload-id shape. Both artifacts targeted the same custom AWS bucket, but the build start returned a UUID (a default/Tigris-issued id) while the test-run start returned a real AWS multipart id. The three build multipart steps disagreed on which account selects the storage backend:
multipart_start→Authentication.authenticated_subject_account(conn)(the caller’s account)multipart_generate_url→selected_project.accountmultipart_complete→selected_project.account
Storage routes per-account via s3_config_and_bucket/1: custom bucket if Account.custom_s3_storage_configured?/1, else the default backend. When a project’s account has custom S3 configured but the upload is performed by a subject whose account differs (a user uploading to an org project), start initiated the multipart upload on the default backend (foreign upload id) while the part PUTs and completion targeted the project’s custom bucket — which rejected the foreign id with NoSuchUpload.
This only affects accounts with custom S3 storage configured whose builds are uploaded by a non-matching subject. It has been present since the build-upload feature shipped (7e2168ef8bb).
Why this fix over alternatives
The object key for all three steps is already derived from selected_project.account, so the upload’s lifetime is owned by the project’s account. start was the only step routing to a different account; aligning it with the others is the minimal, internally-consistent fix.
Impact
Builds from projects with custom S3 storage now upload and process correctly regardless of which subject performs the upload. No schema or data-storage change.
How to test locally
Automated coverage added in builds_controller_test.exs: a project owned by an org account distinct from the authenticated user’s account asserts that Storage.multipart_start receives the project’s account, not the caller’s.
mix test test/tuist_web/controllers/api/builds_controller_test.exs
Validation run: 23 tests, 0 failures; mix format clean; mix credo no issues.