Skip to content

fix(api): surface backend error body in upload failures#250

Merged
canyugs merged 1 commit into
zeabur:mainfrom
canyugs:fix/cli-upload-error-propagation
Jun 11, 2026
Merged

fix(api): surface backend error body in upload failures#250
canyugs merged 1 commit into
zeabur:mainfrom
canyugs:fix/cli-upload-error-propagation

Conversation

@canyugs

@canyugs canyugs commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Description

The CLI's /v2/upload, S3 PUT, and prepare calls all swallowed the response body and returned only status code N. When the backend rejects an upload with a structured JSON error (e.g. REQUIRE_PAID_PLAN for exceeding plan upload size), users saw an opaque 400 with no clue about plan limits or how to fix it.

Surfaced by Discord thread 1513102734407106630: same project, same CLI, succeeded at 08:04 UTC, then started failing from 08:22 UTC after the bundle crossed the 50 MiB Free Plan limit. Took ~30 min of backend trace + Stripe + entitlement spelunking on the support side to recover a cause that was literally on the wire — backend already returned:

{"code":"REQUIRE_PAID_PLAN","error":"Upload size 53048320 bytes exceeds your plan's limit of 52428800 bytes. Upgrade your plan to upload larger files.","limit":52428800,"content_length":53048320}

Change

Add util.FormatHTTPError(action string, resp *http.Response) error which reads the body, prefers the JSON error / code fields when present, and falls back to the raw body or status code. Apply at the six call sites:

pkg/api/service.go (existing_service deploy)

  1. create upload session
  2. S3 PUT
  3. prepare

internal/cmd/upload/upload.go (new_project upload)

  1. create upload session
  2. S3 PUT
  3. prepare

Unit-tested across structured errors (with/without code), non-JSON bodies, empty bodies, and JSON bodies missing the error field.

Before / after

Before:

ERROR  failed to create upload session: status code 400

After:

ERROR  failed to create upload session: Upload size 53048320 bytes exceeds your plan's limit of 52428800 bytes. Upgrade your plan to upload larger files. (code: REQUIRE_PAID_PLAN, status: 400)

Out of scope

The two UploadZipToService implementations (pkg/api/service.go and internal/cmd/upload/upload.go) are near-duplicates and worth unifying, but that refactor is bigger than this fix.

Related issues & labels

  • Refs SEI-717

Verification

  • go build ./... clean
  • go vet ./... clean
  • go test ./... clean (new tests in pkg/util/http_test.go)
  • golangci-lint run ./pkg/util/... ./pkg/api/... ./internal/cmd/upload/... 0 issues

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced error reporting during file uploads. Failures during upload session creation, file transfer, or deployment preparation now display clearer, more informative error messages instead of only status codes.
  • Tests

    • Added comprehensive test coverage for error message formatting across multiple failure scenarios.

The CLI's /v2/upload, S3 PUT, and prepare calls all swallowed the
response body and returned only "status code N". When the backend
rejects an upload with a structured JSON error (e.g. REQUIRE_PAID_PLAN
for exceeding plan upload size), users saw an opaque 400 with no clue
about plan limits or how to fix it.

Add util.FormatHTTPError that reads the body, prefers the JSON
`error` / `code` fields when present, and falls back to the raw body
or status code. Apply at the six call sites across pkg/api/service.go
(existing-service deploy) and internal/cmd/upload/upload.go (new
project upload).

After: "failed to create upload session: Upload size 53048320 bytes
exceeds your plan's limit of 52428800 bytes. Upgrade your plan to
upload larger files. (code: REQUIRE_PAID_PLAN, status: 400)"

Refs SEI-717.
Copilot AI review requested due to automatic review settings June 7, 2026 12:35
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a reusable HTTP error formatting utility and integrates it into the upload workflow. A new FormatHTTPError function in pkg/util/http.go parses structured JSON error responses containing code and error fields, formatting descriptive error messages that include the action context, error details, and HTTP status. When JSON parsing fails, it falls back to raw response body or status code. The utility is then applied to three failure paths in both internal/cmd/upload/upload.go and pkg/api/service.go, replacing inline error formatting. Unit tests cover multiple response scenarios including structured JSON with and without codes, non-JSON content, and empty responses.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: improving error handling to surface backend error bodies in upload failures, which is the core objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds a shared HTTP error formatter so upload-related API calls surface backend-provided error bodies (including structured JSON errors) instead of only reporting a status code, improving diagnosability of /v2/upload, S3 PUT, and prepare failures.

Changes:

  • Introduce util.FormatHTTPError(action, resp) to parse JSON {error, code} responses and fall back to raw body/status.
  • Apply the formatter to upload failure paths in both existing-service deploy and new-project upload flows.
  • Add unit tests covering structured/unstructured/empty response bodies.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
pkg/util/http.go Adds FormatHTTPError helper to preserve server error bodies in CLI errors.
pkg/util/http_test.go Adds unit tests for structured JSON errors and fallback formatting behavior.
pkg/api/service.go Uses FormatHTTPError for non-success responses in existing-service upload flow.
internal/cmd/upload/upload.go Uses FormatHTTPError for non-success responses in new-project upload flow.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/util/http.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 67c4b429-97bf-4b15-b338-69476b4106f4

📥 Commits

Reviewing files that changed from the base of the PR and between 40f7abc and ffdda34.

📒 Files selected for processing (4)
  • internal/cmd/upload/upload.go
  • pkg/api/service.go
  • pkg/util/http.go
  • pkg/util/http_test.go

Comment thread pkg/util/http.go

@zeabur-review-agent zeabur-review-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review Summary (consolidated from team discussion)

Verdict: ✅ Approve (5 approve, 1 request-changes on pre-existing code)

Actionable suggestion (optional, low priority)

  1. Cap io.ReadAll with LimitReaderpkg/util/http.go:21 does an unbounded read. A misbehaving proxy could return a multi-MB HTML page. One-liner fix:
    body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) // 1 MiB cap

Non-blocking observations

  • The resp variable reuse in the prepare step (both upload.go and service.go) is confusing but pre-existing — best addressed in the planned dedup refactor.
  • Structured errors use (status: %d) while raw-body fallback uses "status code %d". Arguably intentional (metadata vs. primary message), but could be unified if consistency is preferred.
  • pkg/api/zsend_rest.go already has a near-identical "parse error JSON from non-2xx body" implementation with a different format. Natural convergence target once this helper lands.
  • Several other endpoints (release.go, template/get/get.go, template/deploy/deploy.go, validate.go) have the same bare-status pattern — good follow-up targets for FormatHTTPError.

What we liked

  • Minimal, focused fix — does exactly what it says, no scope creep.
  • Graceful fallback chain: structured JSON → raw body → bare status code.
  • Excellent test coverage with contract-focused assertions (including the real REQUIRE_PAID_PLAN payload).
  • Exemplary PR description: real incident context, before/after, explicit scope boundaries, verification steps.

Ship it! 🚀

@canyugs

canyugs commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@canyugs canyugs merged commit 19b605f into zeabur:main Jun 11, 2026
5 checks passed
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