Trove staging and production · July 2026

Why staging doesn’t match production—and how we fix it.

Staging is supposed to be a dress rehearsal. Today, Trove can test one version of the product and independently ship another, while the frontend, dashboard, landing page, and backend may also represent different moments in the codebase.

!

The core problem is release identity: there is no enforced rule connecting “the version we approved on staging” to “the version deployed to production.” This is primarily a release-process issue—not evidence of Clerk or Cloudflare caching malfunction.

The promise

What staging should—and should not—match

Must match

The product being released

  • The same source revision across affected apps and backend.
  • The same dependency lockfile and pinned build tools.
  • The same production-shaped build and request paths.
  • A known release identity that can be verified after deploy.
May differ

The environment-specific configuration

  • Staging and production data.
  • Convex deployment URLs and Clerk instances.
  • Analytics projects, domains, and secrets.
  • Scale and real-world user/device behavior.

The realistic promise: staging cannot reproduce every production condition, but it must test the exact source revision we intend to ship, with only deliberate environment configuration changing.

What is broken today

Why the two environments produce different experiences

1 · Different source

Testing and shipping are separate actions

A feature or staging deployment can be tested, then the branch can change or a different checkout can be used for production. Nothing currently requires production to use the approved staging revision.

2 · Laptop builds

Deploy commands trust local state

The scripts build whatever is in the operator’s workspace and explicitly allow dirty files. A deployment can therefore differ from its recorded Git SHA or come from a stale checkout.

3 · Component skew

Each surface deploys independently

Player, dashboard, landing, and Convex have separate commands. Staging can pair a new frontend with an older backend—or vice versa—despite each individual deployment succeeding.

4 · Mutable staging

“Staging looks good” has no durable meaning

With several people deploying, the shared staging URL can change during testing. Approval does not currently identify the exact deployment that was reviewed.

5 · Test coverage

Environment-specific bugs still need checks

Current CI does not build the frontends or test the deployed release. Browser and device differences—such as the landing animation’s Safari/low-power behavior—can still create production-only symptoms.

Not every reported difference has one cause. The release process explains why untested source can ship. The landing animation also has an application-level browser behavior. Production data and scale can reveal additional issues even after source parity is fixed.

Concrete case

Hen tested one landing version; production received another

The recorded landing-staging deployment was b6fbd59. Five commits were then added before the feature merged and production deployed db7054e. The landing source differed by 265 insertions and 51 deletions across five animation files.

b6fbd59
Version recorded on landing staging
This is the deployment associated with the successful staging experience.
version tested
+ 5 commits
The feature branch continued changing
Included CSS clipping, landing adjustments, and integration changes.
not in that recorded staging deploy
db7054e
Version recorded in production
It contained the final branch tree, not the earlier staged version.
version shipped

What this proves: the recorded staging and production source differed materially. It does not prove every environment problem has the same cause, but it proves the release process permits “tested version ≠ shipped version.”

Recommended release path

Create one identifiable release candidate

Feature branch + optional preview
Merge into main
Clean deploy of main SHA X to staging
Test + approve SHA X
Deploy SHA X to production
Before the merge

Feature previews are early validation

A temporary URL for one feature branch—like Hen’s dedicated landing-staging site—is useful for design review. It is not final release approval because that branch can continue changing.

After the merge

Shared staging is the final rehearsal

Deploy the resulting main SHA and all affected components together. Approval identifies that SHA or immutable deployment URL. A newer merge creates candidate Y; it cannot silently replace approved candidate X.

If the team keeps the long-lived staging branch for now: treat its testing as preliminary. Promote to main, deploy the resulting main SHA back to release staging for a final smoke test, then deploy that exact SHA to production.

Practical changes

What we should implement

1 · Clean deployment

Build in GitHub Actions

Use a clean checkout, pinned Bun version, and frozen lockfile. Local laptop deploys become an explicit emergency path rather than the normal release mechanism.

2 · One candidate

Coordinate affected components

Player, dashboard, landing, and Convex changes deploy from the same monorepo SHA, in a defined order, so frontend/backend versions cannot drift silently.

3 · Controlled promotion

Production accepts only staged SHA X

A manual “Promote to production” action receives the successful staging candidate—not an arbitrary local checkout or whatever staging happens to show later.

4 · Observable

Expose release identity

Record the SHA for every deployed component. A small version endpoint or deployment summary makes mismatches visible without Cloudflare and Git archaeology.

5 · Serialized

Prevent deploy races

Only one staging or production deployment runs at a time. New merges create new candidates instead of overwriting the meaning of an existing approval.

6 · Tested

Add a small smoke-test set

Verify frontend builds, app availability, authentication handshake, one known tangle flow, component version alignment, and key landing behavior on Chromium and WebKit.

Supporting decision

Where squash merging fits

Keep squash

Clean history, weaker ancestry

The incident evidence shows the final feature head, the squash into staging, and the squash into main had identical repository trees. Squash did not alter the shipped code; it made the lineage harder to inspect.

Enable merge commits

Clearer ancestry, same release requirement

Future feature commits remain visible in main, making audits easier. Existing history is unchanged, and this still does not stop post-test commits, dirty laptop deploys, or component skew.

Recommendation: choose squash versus merge commits based on which Git history the team understands best. Either choice is compatible with the fix. The non-negotiable rule is: test the final integrated SHA on staging, then ship that same SHA.