Back to Feed
article 3m read

Releases never owned the browser the worker depended on

The one worker that drives a headless browser only learned at job time whether a browser existed, because the browser lived in a user cache instead of the release. I sealed it into the release with a manifest, made health refuse promotion without it, and then caught my own guard looking for it in the wrong folder.

Article DepthPractice

Flow

Runtime guard decision order

Each failed condition has its own reason code so health output explains itself.

  1. 1Marker is a regular file

    A symbolic link or missing marker fails immediately; in production a missing marker never falls back to a cache.

  2. 2Contract parses

    Unknown contract versions, bad hashes or malformed sizes are rejected as invalid.

  3. 3Paths stay inside

    Absolute paths, backslashes and parent segments are refused; resolved paths must sit under the release root.

  4. 4Executable is right

    A regular file with the recorded size and an execute bit, inside a real browser directory.

  5. 5Library matches

    The installed automation library version must equal the manifest value before the browser is declared ready.

One worker that drives a real browser couldn't tell if it had a browser until a job needed it.

The worker logs into the stores' register in a headless Chromium and posts loyalty points. Its code went through my whole release pipeline. The browser didn't. A deploy script installed it into a cache in the service account's home folder, and the worker just checked that a big enough file sat there. If not, the job failed with a clear error.

What was wrong with that

The error showed up late. Health only checked the database, so a release could go live before any job noticed the browser was gone. A big file isn't proof it's the right version or that it starts. And anything that touched that home folder could change the browser without a deploy.

Putting the browser in the release

I replaced the loose install commands with one installer script that CI and the release plans both use. When the release is built, the browser gets copied into a folder inside it. The installer starts that copy once, then writes a small manifest at the top of the release: which browser, where it is, its size, its hash and the library version it goes with. The manifest is written last, so if it's there, the browser started.

Every pipeline step after that, from staging to promotion, checks the manifest and re-hashes the browser.

Checking at runtime

The app checks the release too. The manifest has to be a real file, its paths have to stay inside the release, and the browser has to have the right size and be runnable. The library version has to match. Each failure gets its own reason. In production a missing manifest is a failure, never a cue to fall back to the cache.

Then health uses the answer.

In production, health now needs both the database and the sealed browser to be fine, and answers unavailable otherwise.

A release without a working browser now fails its smoke check and never goes live. The worker also starts the browser from the manifest's path, not the library's default.

My check looked in the wrong folder

My first check used the folder the app was running from as the release root. The web server actually starts two folders deeper, so it would have called every good release broken. Now it recognizes that one exact layout and steps up to the real root. It doesn't hunt through parent folders, because then a stray file outside the release could pass.

Two more fixes that day

In CI, the temp folder sat deep inside the workspace. Chromium puts a socket file in there, and socket paths have a length limit, so a deep enough path makes the browser crash on start. Each run now gets a short temp folder that gets deleted afterward.

The test that starts a real browser was running beside lots of parallel tests. It now runs alone, first.

The runtime check still skips the hash, so a same-size swap after release would get past it.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward