MidstreamDocs
When something goes wrong

Why a deploy failed

What each deploy error means, whether it is something in your project or something on our side, and what to do next.

A deploy failed and you have an error code. This page says what the code means and what to change. Most failures come from the project's own build or test — and most of those are the same one, so start there.

The test ended before it reached the scene

Error code SANDBOX_TEST_FAILED. This is the most common failure by a wide margin.

To stand up an instance we run your test and stop it at the scene, then capture the session at that moment. If the test finishes — passing or failing — without ever reaching the scene call, there is nothing to capture and the deploy fails.

Four things cause it, in rough order of frequency:

  1. The test fails before the scene. An assertion or a step earlier in the test throws, so the test ends early. The deploy's output is your own Playwright output; read it in the health view and you will see the failing step.
  2. A selector changed. The app moved on, and a step before the scene no longer finds what it is looking for. The test may pass in your own CI against a different data set and still fail here, where the app starts empty.
  3. The slug does not match. A deploy halts the test at one exact slug. If the scene call in the test now spells the slug differently, the test runs past it and ends normally. Renaming a slug also starts a new scene, so the old one keeps deploying against a call that no longer exists.
  4. The test needs data that is not there. A deploy is a fresh container holding your repository at one commit. Anything the test relies on — a database, a seeded user, a fixture — has to be created by your install or start command.

The fastest check is local. Run:

npx midstream your-scene-slug

That runs the same test headed and pauses at the scene. If it does not stop there on your laptop, it will not stop there in a deploy.

If the test file itself has moved or been renamed, you get a different code — SANDBOX_TEST_FILE_NOT_FOUND — because we look for the file before running anything.

Where to read the error

Open the project and go to Deploy health in the sidebar. Every deploy is listed newest first, with its error code. Open one for the phase timeline and the raw output from your own build. Workspace members see that output; people who only opened an instance link do not.

The health view is covered in Reading a deploy's timeline.

The error catalog

Every failure maps to exactly one code. "Whose side" is what the code itself records — it decides whether the person waiting on the instance is told their project has a problem or that something went wrong on our end.

Before your code runs

CodeWhat happenedWhose sideWhat to do
MACHINE_BOOT_TIMEOUTThe deploy never reported anything back at all, and we gave up waiting.OursRetry it. If it happens twice on the same scene, tell us.
DEPLOY_INTERNALWe failed while setting the deploy up, before your code was involved.OursRetry it. Repeats are worth reporting.
UNKNOWNA deploy reported a failure without saying what it was.OursRetry it, and tell us which scene — we want to see these.

Fetching your code

CodeWhat happenedWhose sideWhat to do
SANDBOX_GIT_CLONE_FAILEDWe could not fetch the repository at that commit.OursUsually a network blip — retry. If it keeps failing, check that the commit still exists and that the GitHub App is still installed on the repository.
SANDBOX_GIT_CLONE_TIMEOUTFetching the repository took longer than we allow.OursRetry. A very large repository or a large file history is the usual reason it is slow enough to matter.

We do not blame your project for a failed fetch: from inside a deploy we cannot tell a network blip from a deleted branch, and the blip is the common case.

Reading midstream.json

CodeWhat happenedWhose sideWhat to do
SANDBOX_CONFIG_MISSINGThere is no midstream.json in the repository root at that commit.Your projectAdd it and push. Note that it has to exist on the branch being deployed, not just on your default branch.
SANDBOX_CONFIG_MALFORMEDThe file is not valid JSON, or is missing an install or start command.Your projectBoth keys are required and both must be non-empty strings. A trailing comma is the usual culprit.

Installing

CodeWhat happenedWhose sideWhat to do
SANDBOX_INSTALL_FAILEDYour install command exited with an error.Your projectThe command's output is on the deploy. Read it as you would a failed CI job. A lockfile that disagrees with package.json and a private registry that needs credentials are the two we see most.
SANDBOX_INSTALL_TIMEOUTYour install command ran past its deadline.Your projectRetry first — a slow registry is often a one-off. If it is consistently slow, see Time limits.

Installing the browser

Before your test runs, we run your project's own playwright install chromium. Playwright pins a Chromium build to each of its versions, so the browser your suite needs is decided by the Playwright version in your package.json, not by ours. The container ships one build; if yours is a different one, this is where we fetch it. It downloads nothing when the build is already there, which is the usual case.

A project with no Playwright of its own out here — one whose test command runs inside Docker, say — skips this step rather than failing it.

CodeWhat happenedWhose sideWhat to do
SANDBOX_BROWSER_INSTALL_FAILEDFetching the Chromium your Playwright asks for failed.OursRetry it. If it keeps failing, check that npx playwright install chromium works in your own CI on the same version.
SANDBOX_BROWSER_INSTALL_TIMEOUTThat download ran past its deadline.OursRetry it.

Starting your app

CodeWhat happenedWhose sideWhat to do
SANDBOX_START_FAILEDYour start command exited before the app ever served a request.Your projectThe deploy names the command it ran and shows what it printed before dying. A missing environment variable, a failed migration, or a port already in use are the common reasons.
SANDBOX_START_TIMEOUTThe start command kept running, but nothing ever answered on the port.Your projectRead the start command's output on the deploy — the app is alive, so whatever it last printed is the best clue. Your app must bind to the port given in the PORT environment variable, and must bind to it quickly. A server that only listens on 127.0.0.1 after a long build step is the classic case — build during install, not during start.

Running the test

CodeWhat happenedWhose sideWhat to do
SANDBOX_TEST_FILE_NOT_FOUNDThe test file recorded with the capture is not in the repository at that commit.Your projectThe file moved or was renamed after the capture. Push a new capture from a commit where the file exists at its new path.
SANDBOX_TEST_FAILEDThe test ended without reaching the scene.Your projectSee the section above.
SANDBOX_TEST_TIMEOUTThe test ran past its deadline without reaching the scene, so we stopped it.Your projectUsually a step waiting forever for something that never appears. Reproduce with npx midstream your-scene-slug and watch where it hangs.

Finishing up, and after the app is live

CodeWhat happenedWhose sideWhat to do
SANDBOX_STATE_UPLOAD_FAILEDWe captured the session but could not save it or report the instance as ready.OursRetry it.
SANDBOX_SETUP_TIMEOUTThe whole deploy ran past its overall deadline without any single step failing.OursRetry it. If it recurs, the deploy is genuinely slow rather than stuck — see Time limits.
SANDBOX_APP_CRASHEDYour app started, served traffic, then crashed repeatedly.Your projectSomething in the app is failing at runtime rather than at boot. The deploy keeps your app's output from startup only, so it may not show the crash itself — tell us the scene and we can look at the container's own logs.
MACHINE_UNAVAILABLEThe instance was live, and what was running it went away.OursRetry it. The next deploy stands the instance back up from scratch.

Transient failures, and retrying

Some codes are marked transient in the health view. That means re-running the same deploy has a real chance of working: a network blip, a slow registry, an instance that vanished mid-flight. A failure that is not marked transient is deterministic — midstream.json will still be malformed on the second try.

Nothing retries by itself today. To retry, open the failed deploy in the health view and press Retry provision. That starts a fresh deploy against the same capture and leaves the failed one in the history, so "it failed twice and then worked" stays answerable.

Retrying requires workspace membership. Someone who only followed an instance link cannot retry — they need to ask a member.

"I'm signed out inside the instance"

This one is not a deploy failure — the deploy worked, and the app came up signed out or missing state. It is worth its own explanation, because the cause is never obvious.

When your test reaches the scene, we capture the session as it stands: cookies, localStorage, and sessionStorage. When someone opens the instance, we hand that session back to their browser. But an instance runs at its own hostname, so every captured cookie is re-issued against that hostname. Three consequences follow:

  • A cookie scoped to another domain does not carry. If your app signs users in through a cookie on auth.example.com and reads it from app.example.com, that cookie exists in the instance only at the instance's own hostname. Requests your app makes to the original domain will not carry it.
  • Third-party cookies do not carry. Anything an embedded widget, payment provider, or analytics script set during the test is gone. Those services will see a brand-new browser.
  • A cookie with no expiry gets one. Session cookies — the ones meant to disappear when the browser closes — are re-issued with a 24-hour lifetime, so they survive the reload that seeds the instance.

localStorage and sessionStorage are restored as they were, key for key.

The practical fix is to make the test sign in the way the instance can replay: against your own app, at your own domain, with a first-party cookie or a token in localStorage. If your test authenticates by calling an external identity provider, the instance will land signed out no matter how well the deploy went.

There is a second, better reason to do it that way, and it is covered in Security and your data: a captured session is a real session. Use test accounts.