---
title: "The Stale Bundle That Passed Every Green Check on develop"
canonical: https://dxdev.com/blog/stale-artifacts-after-merge/
datePublished: 2026-06-23
---
At 10:22 AM on June 23, we marked the Customize Columns UI live on `develop`. The JavaScript bundle the browser loaded was missing the opt-in changes that had just been merged.

The ticket was done and the branch was merged. The UI had reached the environment where we expected to use it. The source said one thing, and the code the browser received said another. Every status signal was green while the application served yesterday's behavior.

## What the browser actually loaded

The symptom was specific. The loaded bundle did not contain `develop`'s opt-in changes. The merged source and the compiled JavaScript being served disagreed.

Our application still has a compiled-bundle path in the release chain. Source changes alone don't ship. The JavaScript has to be rebuilt, and the application reference has to point at the dated compiled output. A ticket can be correct in the repository and wrong in the browser if those two drift apart. Here they had.

The immediate fix was to recompile the bundle correctly, which restored the missing opt-in behavior. That repaired one release. It didn't answer what would stop the same mismatch from reaching `develop` again.

## Trusting the merge

A merge confirms that source control accepted a set of changes. It says nothing about whether a browser will load the artifact built from them.

A manual visual check after each release can catch this, and it did here. But it catches it after the close process has already declared success. It also depends on someone remembering the exact behavior that should appear on the page. That is a weak control for a mechanical failure.

The signal we needed was already available: file modification time.

## The guard

We added a check to the item-close process. Before it lets a release ship, it compares the compiled artifact's modification time with the source it represents. If the bundle is older than its source, close stops. There is no warning to read later and no assumption that a clean merge implies a current build.

The guard deliberately checks freshness, not feature semantics. It can't prove every branch is correct. It proves one narrow invariant: the compiled JavaScript was created after the source that feeds it.

That scope is on purpose. A generic check that tried to infer whether a UI “looks right” would be brittle and expensive. A timestamp comparison is simple, deterministic, and aimed at the failure that actually happened.

## Conflicts on the bundle reference

The incident also changed how we read conflicts around compiled assets. A conflict on the compiled-bundle reference is a prompt to verify that source and bundle match before closing the item.

The mechanical sequence is now: compile the dated JavaScript bundle, flip the page's bundle reference, then commit. The build goes last because it produces the release artifact from the final source state. Building earlier lets later edits make the bundle stale again before the commit lands.

Modification times don't solve deployment in general. This guard fits a system where source files and checked-in compiled output both take part in the release. In that system, source control can report a successful merge while the delivered JavaScript is obsolete.

We crossed the line between the code we changed and the code the browser ran without proving it once. The close process now refuses to let us do it again.

If your repository checks compiled output in alongside source, look at whether your release process compares the age of the artifact with the age of its source and stops when the bundle is older. Build the bundle last, after the final source edits.
