#6396·webstudio

fix: Publish fails with a 504 on loadProjectBundleByBuildId above a content-collection threshold

Author: webstudio-issue-reporter[bot]Created Sep 16, 2026Updated Sep 17, 2026

User story

As a Webstudio user with a multilingual site, I want to publish a project that uses several content collections, so that translated content can go live.

Summary

Publishing fails deterministically once a project combines six or more indexed content collections with a moderately sized content database. The deploy runner receives an HTTP 504 with an HTML error page from the project-bundle endpoint and exits before anything is built, so the publish job is reported only as status failed with no error detail through the API.

What the agent tried

  1. Publish a project that has nine indexed content collections; the job is created and then reported as failed within seconds.
  2. Query the publish job through the API; only status failed is returned, with no message, so the cause is invisible from the client side.
  3. Reproduce the generated site locally with the same CLI version and the SaaS build templates; scaffolding and bundling succeed with correct routes, so the project data itself is valid.
  4. Build a minimal reproduction in an empty project: six asset folders, each a collection with collection.json, template.mdx and 42 entries whose only fields are title, slug and a 1500-character excerpt; six dynamic pages as the collections' entryPages; six assets resources on the home body with result many, limit 200, output fields title/slug/excerpt and content mode none.
  5. Publish the minimal project at different sizes and collection counts and record the outcome.

Expected behavior

Publishing should succeed for any project whose content database is within the advertised limit, or, if a limit is exceeded, the publish job should return an actionable error naming the limit instead of a gateway timeout.

Actual result

The deploy runner runs webstudio sync, which calls build.loadProjectBundleByBuildId and receives HTTP 504 Gateway Timeout with an HTML error page. The client raises a TRPCClientError because HTML is not valid JSON and exits with code 1, so the job is marked failed before the build starts. In the builder this surfaces only as a JSON parse error about an unexpected '<' character. The same project also makes api.assetQueries.preview return a 504 after about 17 seconds, while loadProjectBundleByProjectId returns the same 5.45 MB bundle in about 3.8 seconds.

Recovery attempts

  1. Retried the publish five times over 15 minutes for both staging and production targets; every attempt failed identically.
  2. Ruled out project data by building locally with the SaaS templates and by running the project audit, which reported zero errors.
  3. Ruled out page count and build size by inflating a clone to 40 pages, 5496 instances and 3531 KB of build data, which published successfully.
  4. Ruled out document count, resource count and the number of unindexed folders by raising each past the original values, which published successfully.
  5. Narrowed the trigger by deleting collections one pair at a time across four clones, then reproduced it from scratch in an empty project.

User impact

A site cannot be published at all. There is no error message anywhere in the client, so users cannot tell whether their project is broken or the service is; diagnosing it took many hours of bisecting across several project copies. Any project that stores each language in its own collection hits this quickly.

Technical context

Measurements from the minimal reproduction, where the only variables are the number of collections and the content-database size reported as assetIndex.database.unboundedBytes: 6 collections, 138 docs, 36,819 bytes -> success 6 collections, 242 docs, 208,213 bytes -> failed 5 collections, 215 docs, 195,859 bytes -> success 6 collections, 258 docs, 234,798 bytes -> failed Deleting one collection makes it publish again; re-adding the same collection breaks it again.

The same pattern on copies of a real project: 9 collections, 90 docs, 305,200 bytes -> failed 7 collections, 84 docs, ~292,000 bytes -> failed 6 collections, 76 docs, 276,292 bytes -> failed 6 collections, 60 docs, 196,214 bytes -> failed 5 collections, 68 docs, 264,978 bytes -> success 5 collections, 57 docs, 199,928 bytes -> success 3 collections, 30 docs, 112,340 bytes -> success

Neither dimension alone explains it: six collections at 37 KB is fine, five collections at 265 KB is fine, six collections at about 200 KB is not. database.maxBytes reports 512,000 throughout and is never approached. The collections involved are structurally identical to one another: same schema properties, same x-webstudio block, same resource queries, and every $ref resolves.

Three smaller issues found while investigating:

  1. A collection created through the CLI or MCP is recognised as a collection, since further uploads are refused with "Use New entry to add files to a collection folder", but its entries never appear in assetIndex.documents unless a resource queries that folder. This makes a deleted collection impossible to restore faithfully.
  2. delete-asset-folder on a folder that still contains assets fails with 'update or delete on table "AssetFolder" violates foreign key constraint "Asset_folderId_fkey"' and leaves the folder partially emptied.
  3. revert-to-restore-point requires a reviewed destructive confirmation token, but its input schema accepts only id, with no confirmDestructive or confirmationToken fields as delete-page has. A revert can therefore be planned but never committed through MCP.

Agent environment

  • Client: Claude Code
  • Provider: Anthropic
  • Model: claude-opus-5[1m]
  • Reasoning effort: unknown
  • Trigger: user-requested
  • Category: hang-or-crash

Technical runtime

  • CLI: 0.298.0
  • Node.js: 23.7.0
  • Operating system: darwin 25 (arm64)
  • Execution mode: mcp
  • API contract: public-api:1shmyoc
  • Bundle contract: bundle-1x6rafk

Captured failure diagnostics

  • Tool: update-props
  • Error code: BAD_REQUEST

Acceptance criteria

  1. A project with many content collections publishes successfully, or the publish job fails with an explicit, actionable error naming the exceeded limit.
  2. The project-bundle endpoint used by the deploy runner does not time out for bundles that the by-project-id endpoint serves in a few seconds.
  3. publish.getJob returns an error message or failure reason alongside status failed, so clients can surface the cause.
  4. If a content-database or collection limit exists, it is documented and reported in assetIndex.database rather than only surfacing as a gateway timeout.
  5. delete-asset-folder either cascades to contained assets or fails atomically without partially emptying the folder.
  6. revert-to-restore-point exposes confirmDestructive and confirmationToken so a planned revert can be committed.