Skip to content

Studio Push fails with HTTP 413: studio-file-uploads endpoint rejects uploads over ~3.5 MB (surfaced as Unknown error) #3867

@henryperkins

Description

@henryperkins

Summary

studio push (and the desktop Push) fails for any site whose sync archive exceeds ~3.5 MB. The WordPress.com upload endpoint

POST https://public-api.wordpress.com/rest/v1.1/studio-file-uploads/<SITE_ID>

returns 413 Request Entity Too Large on the very first TUS creation request — before a single byte of the archive is uploaded. Studio does not report this usefully: the CLI prints the raw tus: error, and the desktop app collapses it to "Unknown error" / "Studio was unable to connect to WordPress.com."

I isolated this fairly thoroughly (details below). The 413 is driven purely by the declared Upload-Length header — it is independent of archive contents, file types, site, plan, and request rate. A ~3.5 MB ceiling is far too small for a feature that syncs whole WordPress sites, so this looks like a server-side regression/misconfiguration on the studio-file-uploads endpoint, compounded by poor client-side error handling.

Environment

  • Studio 1.11.0
  • Reproduced via both the real studio push and standalone curl (so it's server-side / platform-independent)
  • Account has 15 sites (Free, and Business/Atomic) — all show the identical cap

Expected vs. actual

  • Expected: A 106 MB site archive uploads and imports (this worked previously).
  • Actual: 413 at TUS upload creation; push never starts. Error shown to the user is non-actionable.

Real CLI output:

✔ Site exported successfully
✖ Push failed: tus: unexpected response while creating upload, originated from request
  (method: POST, url: https://public-api.wordpress.com/rest/v1.1/studio-file-uploads/<SITE_ID>,
   response code: 413, response text: , request id: n/a)

Steps to reproduce

  1. Have a local site whose sync archive is larger than ~3.5 MB (i.e. essentially any real site).
  2. studio push --remote-site <SITE_ID> --options all
  3. Export succeeds; upload fails immediately with 413.

Root cause (isolated)

The endpoint rejects the creation request based solely on the declared Upload-Length. Replaying just the TUS creation POST with an empty body (no file data sent), varying only Upload-Length:

Declared Upload-Length Response
1 KB … 3,000,000 B 201 Created
3,529,728 B 201 Created ← last accepted
~3,600,000 B and up (4 MB, 10 MB, 50 MB, 106 MB, 5 GB) 413

So the cap sits at ≈3.5 MB, it is deterministic, and it is evaluated after authentication (a request with no token returns 403, not 413).

Minimal standalone repro (no Studio needed)

TOKEN="<accessToken from ~/.studio/shared.json -> authToken.accessToken>"
SITE="<numeric wpcom site id>"
META="filename $(printf push.tar.gz | base64),filetype $(printf application/gzip | base64)"
URL="https://public-api.wordpress.com/rest/v1.1/studio-file-uploads/$SITE"

# 3 MB declared -> 201 Created
curl -s -o /dev/null -w "3MB   -> %{http_code}\n" -X POST "$URL" \
  -H "Authorization: Bearer $TOKEN" -H "Tus-Resumable: 1.0.0" \
  -H "Upload-Metadata: $META" -H "Upload-Length: 3000000" -H "Content-Length: 0"

# 106 MB declared -> 413 (empty body either way)
curl -s -o /dev/null -w "106MB -> %{http_code}\n" -X POST "$URL" \
  -H "Authorization: Bearer $TOKEN" -H "Tus-Resumable: 1.0.0" \
  -H "Upload-Metadata: $META" -H "Upload-Length: 111014960" -H "Content-Length: 0"

What I ruled out

Hypothesis Test Result
Site/archive genuinely too large Site allows 2 GB media uploads, 53 GB storage free; archive is 106 MB Not a real limit
Bundling/export step Export produces a valid archive; 413 is the next step Bundling succeeds
File extension / name / directory No file-type logic exists in the push/export path N/A
A specific file (e.g. a .webp) A real .webp inside a 0.24 MB bundle uploads fully (201 → 204, media-id returned) Content-blind
Site / plan / Atomic specific Probed all 15 sites on the account (Free + Business/Atomic) Identical ~3.5 MB cap everywhere
Rate limiting Burst of 24 interleaved requests in ~14 s: every large → 413, every small → 201, no 429, no Retry-After/X-RateLimit-*; also fails on the very first request Not rate-limited

The 413 response has an empty body, Server: nginx, routed via the Automattic edge (Server-Timing: a8c-cdn …), and the endpoint is VideoPress-backed (x-videopress-upload-* in Access-Control-Expose-Headers).

Why this is hard to diagnose in Studio (client-side issues)

References are to current trunk:

  1. The client's size pre-check is ~1,400× too high to ever warn. SYNC_PUSH_SIZE_LIMIT_GB = 5 (tools/common/lib/sync/constants.ts) is checked in apps/cli/commands/push.ts and the desktop slice, but the server caps at ~3.5 MB — so the pre-check always passes and the user gets no guidance.
  2. A 413 is retried as if it were a transient network error. In tools/common/lib/sync/tus-upload.ts, onShouldRetry returns status !== 403, so a 413 is retried across all retryDelays and (if the upload had started) surfaces as a "network paused" event. 413 is not retryable — the payload won't shrink.
  3. The desktop app discards the status code. apps/studio/src/modules/sync/lib/ipc-handlers.ts parses the error with z.object({ error: z.string() }).safeParse(error); a bare 413 (empty body / tus error object) fails that parse → returns { success: false, error: 'Unknown error' }, and getErrorFromResponse (apps/studio/src/stores/sync/sync-operations-slice.ts) ultimately shows "Studio was unable to connect to WordPress.com." The user never learns it was a 413 or that size was the trigger.

Suggested fixes

Server-side (likely needs routing to the WordPress.com sync / studio-file-uploads team):

  • Raise/correct the studio-file-uploads upload-size limit. ~3.5 MB makes Push unusable for essentially any real site. (Was this recently changed? Pushes of ~100 MB worked previously.)

Client-side (this repo):

  • In onShouldRetry, treat 413 (and other non-retryable 4xx) as terminal — stop retrying and don't show a "network" message.
  • When an HTTP error has no JSON { error } body, fall back to reporting the status code (and a friendly explanation for 413 specifically), instead of "Unknown error" / "unable to connect."
  • Align or remove the SYNC_PUSH_SIZE_LIMIT_BYTES pre-check so it reflects the server's actual limit, and warn the user before exporting a multi-MB archive that will be rejected.

Notes

  • Happy to provide additional captures (full headers, the webp-upload test, the per-site sweep) if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions