Environment Drift
Machines diverge quietly until something works for one person and not another. Detecting drift and pinning the things that matter.
Analysis
Every machine starts the same and none stays that way. The failures this produces are expensive because they are not reproducible by the person investigating.
Where drift comes from
Unpinned toolchain versions. One developer upgraded, one did not.
Global installs that shadow project-local ones.
Environment variables set months ago and forgotten.
Operating system differences, including architecture — an ARM laptop and an x86 CI runner produce different results more often than people expect.
Locale and time zone, which change string sorting and date behaviour.
Leftover state: stale caches, old containers, a database with data from a branch abandoned in spring.
Editor and tool configuration applied locally and never shared.
What it costs
Failures that reproduce for one person. The expensive category, because debugging requires two people and a comparison.
Cache misses, since a different environment produces a different hash and shares nothing.
Green locally, red in CI, which erodes trust in local verification and pushes work into the pipeline.
Onboarding failures, where the setup works on machines that already have the undocumented prerequisite.
Pinning what matters
Not everything needs pinning; these do.
Language runtime version, in a file the tooling reads.
Build tool version, through a wrapper checked into the repository.
Dependency versions, through a lock file that is committed and honoured.
Container base images by digest, not by tag.
Formatter and linter versions, because an unpinned formatter reformats the world when someone upgrades.
Anything whose version appears in a cache key, by definition.
Detecting drift
A doctor command. A script that checks versions and configuration against expectations and reports differences in plain language. Cheap to write, and it turns a two-person debugging session into a one-line answer.
Run it in CI too, so the pipeline environment is checked against the same expectations.
Print the environment in build output: versions, platform, relevant variables. When someone reports a failure, this is the first thing to compare.
Compare cache keys between a local run and a CI run. A difference identifies the drifting input precisely.
Reducing the surface
Project-local tools over global installs, so the repository determines the versions.
A wrapper script as the single entry point, which sets the environment consistently rather than relying on shell configuration.
Containers for dependencies, which removes the whole category for databases and services.
Ephemeral CI runners, so the pipeline cannot accumulate state that the local environment lacks.
The honest limit
Full parity between a laptop and a CI runner is not achievable, and pursuing it past a point costs more than it returns.
Aim for the differences to be known and declared rather than absent. A documented list of accepted differences is worth more than an aspiration to have none, because it tells the next person where to look.
The doctor command
One script that converts a two-person debugging session into a one-line answer.
Check versions: runtime, build tool, container tooling, anything pinned.
Check configuration: environment variables, locale, time zone.
Check access: registry, cache, required services.
Compare against expectations held in the repository.
Report differences in plain language, with the fix.
Run it in CI too, so the pipeline environment is held to the same expectations as a laptop.
Printing the environment
One line in the build output that turns a two-person investigation into a comparison.
Print versions: runtime, build tool, key dependencies.
Print the platform: operating system, architecture.
Print the relevant configuration: locale, time zone, key environment variables.
In both local and CI output, identically formatted.
When a failure is reported, this is the first thing to diff, and it resolves a large share of "works on my machine" without anyone having to reproduce anything.