Let's talk
engineering

Development mode and release mode are different programs

The mobile application had been developed for weeks. It type checked, it bundled, its screens rendered under the development client, and its API calls were verified against a running server.

The first time anybody produced a release package, it failed twice before producing anything.

The first failure was the bundler resolving the wrong entry point. In a monorepo it had inferred its server root from the folders it was told to watch, which put the root above the application, so the embedding step went looking for an entry file two directories up. Development mode never took that path — it starts a server with an explicit root and serves from there.

The second was a build-time preset that the babel configuration required and that was not a direct dependency of the package. Under a package manager that links strictly, an undeclared dependency is simply absent. It had been resolving through the development server, which had it available for its own reasons, and it disappeared the moment anything ran the transform standalone.

Neither was a new bug. Both had been sitting in the repository since the application was scaffolded, invisible, because the code path that reads them had never been run.

The thing that had never been exercised

The lesson is narrow and it generalises further than it looks: a configuration that has only ever run in one mode is not a configuration. It is an untested hypothesis about what a different mode will do.

Development and release are not the same program with a flag. They use different entry resolution, different transforms, different dependency graphs, different bundling, different signing. A project that has never been built for release has an unknown quantity of latent breakage in it, and the quantity does not decrease with time — it grows, because every dependency added in development mode is another thing that might not be declared properly.

The same applies to every environment gap anybody keeps meaning to close: the migration path that has only run forward, the deployment script that has only been run by one person on one machine, the backup that has never been restored. In each case the untried direction is not “probably fine”. It is unmeasured.

The correction is not exotic. Produce a release artefact early, once, before you need one. It will fail, the failures will be configuration rather than logic, and they will take an afternoon instead of the day before a demonstration.

The toolchain problems that pretend to be something else

Two of the obstacles were environmental rather than in the code, and both are worth naming because they present as entirely different problems.

Process creation on Windows caps the command line at the legacy path length, regardless of the long-paths setting being enabled. The setting relaxes some limits and not that one, so a deep dependency tree produces a failure that looks like a missing file.

And a native build reported that it could not identify a C compiler. That message sends you to install a toolchain you already have. It was the same path-length limit, hit on the staging directory for native artefacts. The message describes what the tool concluded, not what happened.

When an error message names something you can verify is present, stop believing the message. It is a symptom description written by a tool that had one hypothesis and reached for it. Both of these went into a document in the repository, because the second time somebody hits them the cost should be five minutes rather than an afternoon.

Verifying the artefact, not the source

Installing the package on a real device was the first runtime verification the mobile application had ever had. Every previous round had ended with an honest note that no screen had been driven on hardware — one round got as far as the app booting under the development client with the first screen rendering, and then scripted input never reached the rendering surface, so the flow under test remained unverified.

What that first device run checked is worth listing, because it is not “does it open”.

The package was installed and its main activity confirmed to be in the foreground. The system log was checked for fatal errors and had none. And the embedded bundle was searched for the API address it had been compiled with, to confirm it pointed at the deployed server and carried no fallback to a local one.

That last check is the one people skip and it is the one that catches the worst class of release defect. A development fallback baked into a shipped bundle is invisible in source review — the source is conditional and the condition looks right — and it produces an application that works perfectly for everyone on the office network and not at all for anybody else. Checking the compiled artefact rather than the code that produced it is the only way to know which branch was taken.

Verify the shipped thing, not the thing that produces it. Grep the bundle. Read the manifest. Check what the artefact actually points at.

On saying what you did not verify

Across several rounds the reports ended with a plain sentence: not runtime-verified on device. Type checked, bundled, every translation key resolved, every endpoint exercised against the live API, and never once run by a person on hardware.

That sentence is uncomfortable to write and it is the most useful line in those reports. It located the risk precisely, and it is why the first device run was scheduled at all rather than being assumed away by a long list of green checks above it.

There is a temptation, when a lot of verification has been done, to let its volume stand in for the kind that is missing. Eleven kinds of static verification do not add up to one runtime execution. They are checking different things, and the one you did not do is the one that finds the entry point resolving two directories up.

Rules

Build a release artefact early, before anything depends on it. The failures will be configuration and they will be old.

Treat any untried direction as unmeasured, not as probably fine. Downgrades, restores, deployments from another machine, release builds.

Distrust an error message that names something you can see is present. It is a tool’s hypothesis, not a diagnosis.

Verify the compiled artefact points where you think. Development fallbacks are invisible in source and obvious in the bundle.

Write down which verification you did not do. A list of green checks does not cover the kind missing from the list.

Working on something like this?

We build this kind of software, and we staff the teams that do.

Get in touch