fix(api): scale S3 upload timeout with payload size#253
Conversation
The upload flow shared a single http.Client{Timeout: 30s} across the
create-session, S3 PUT, and prepare calls. Client.Timeout covers the
entire request including body transfer, so a ~50MB zip required
~14 Mbps sustained upstream to finish in time — large uploads always
failed on slow links, with no flag to raise the limit.
Keep the 30s client for the two small API calls; give the S3 PUT its
own context deadline via util.UploadTimeout: a 2-minute floor plus 1s
per 256 KiB of payload (~5.3 min for 50 MiB). Applied to both copies
of the flow (pkg/api/service.go and internal/cmd/upload/upload.go).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR replaces the fixed 30-second timeout for S3 uploads with a size-based timeout. A new 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Pull request overview
This PR addresses intermittent context deadline exceeded failures during S3 uploads by removing the fixed 30s whole-request deadline from the large S3 PUT and replacing it with a size-scaled context timeout, while keeping the existing 30s http.Client timeout for the small Zeabur API calls.
Changes:
- Add
util.UploadTimeout(contentLength)to compute a size-scaled upload deadline. - Apply the size-scaled context timeout to the S3 PUT request in both upload flows (API client and
zeabur uploadcommand). - Add unit tests for
UploadTimeout.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| pkg/util/http.go | Adds UploadTimeout helper used to size the S3 PUT deadline. |
| pkg/util/http_test.go | Adds tests covering UploadTimeout behavior. |
| pkg/api/service.go | Uses a size-scaled context deadline for the S3 PUT in the API upload flow. |
| internal/cmd/upload/upload.go | Uses a size-scaled context deadline for the S3 PUT in the CLI upload flow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func UploadTimeout(contentLength int64) time.Duration { | ||
| return 2*time.Minute + time.Duration(contentLength/(256*1024))*time.Second | ||
| } |
| {"zero size gets the 2min floor", 0, 2 * time.Minute}, | ||
| {"tiny payload still gets the floor", 1024, 2 * time.Minute}, | ||
| {"1 MiB adds 4s over the floor", 1 << 20, 2*time.Minute + 4*time.Second}, | ||
| {"50 MiB scales to ~5.3 min", 50 << 20, 2*time.Minute + 200*time.Second}, | ||
| {"1 GiB scales to ~70 min", 1 << 30, 2*time.Minute + 4096*time.Second}, |
Code Review Synthesis — #253PR: fix(api): scale S3 upload timeout with payload size Panel Verdict: APPROVE (5/7 reviewed, 4 approve, 1 request changes)SummaryThe PR replaces the fixed 30s Findings🔴 Suggested Changes (from Legolas — request changes)
🟡 Nit
🟢 Info (consensus observations)
RecommendationThe PR is safe to merge. Legolas's variable-shadowing concern is valid for readability but does not affect correctness. Consider addressing it in a follow-up or as part of this PR at maintainer discretion. [done] |
Problem
zeabur deploy/zeabur uploadintermittently fail with:The upload flow shares a single
http.Client{Timeout: 30 * time.Second}across three requests:http.Client.Timeoutcovers the full request including body transfer, so large uploads always fail on slow links and there is no flag to raise the limit. Forum report: user in Taiwan uploading ~50 MiB, 12 failures vs 3 successes in one evening (failures clustered at residential peak hours); they gave up and switched to GitHub-based deploy.Prior art
http.Client.Timeoutfor S3 transfers — cancellation is context-driven, sized to the operation.Solution
Keep the 30s client for the two small API calls. Give the S3 PUT its own context deadline that scales with payload size via
util.UploadTimeout:Applied identically to both copies of the flow (
pkg/api/service.goandinternal/cmd/upload/upload.go).Why not a flag?
A
--upload-timeoutflag pushes the failure onto the user (they only learn about it after the error). A size-scaled ceiling fixes the common case with zero configuration; a flag can still be added later if someone hits the conservative floor.Caveat verified
Presigned URL expiry is signed server-side at 30 minutes (
upload_v2.go:s3.WithPresignExpires(30*time.Minute)). The PUT starts seconds after signing and S3 validates the signature at request start, so the upload window is safe. The timeout only exceeds 30 min beyond ~420 MiB, far above plan limits.Validation
TestUploadTimeoutwritten first (5 cases: floor, scaling at 1 MiB / 50 MiB / 1 GiB)go build ./...,go vet ./...,go test ./...all cleanRelated issues & labels
🤖 Generated with Claude Code
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by CodeRabbit
New Features
Tests