← Our Failures'Fixes do nothing live' is almost always a stale /Applications binary, not wrong source
intermediate6 min read · updated 2026-06-20
Market & numbers — every figure sourced
wasted_debug_hours_per_incident2 hoursest: BLA internal incident-log median: time spent re-reading correct source before checking the installed binary, across recurring stale-binary incidents
fix_time_once_diagnosed5 minutesest: BLA internal: rebuild + reinstall + re-sign of a single .app once the stale binary is identified as the cause
'Fixes do nothing live' is almost always a stale /Applications binary, not wrong source
You edit the code. The edit is correct. You save. You run the app. Nothing changes. So you assume the source is wrong, and you go back and rewrite a fix that was already right. This is the single most expensive trap in desktop app work, and it has almost nothing to do with your code.
The pattern wasted roughly 2 hours per incident in our own logs before we made it a checklist item. Once you know what to look for, the actual fix takes about 5 minutes.
What we tried
We shipped a small native macOS app (Swift, a `.app` bundle living in `/Applications`). A user reported a bug. We found the bug in source, fixed it, confirmed the fix was correct by reading the diff, and told the user it was resolved. The user ran the app. The bug was still there.
Our instinct — the wrong one — was: "the source must still be wrong." So we re-read the file, re-reasoned about the logic, and sometimes rewrote a perfectly good fix into a worse one. We were debugging code that was already correct.
What broke
The source was never the problem. The thing the user double-clicks is the compiled, installed binary inside `/Applications/YourApp.app/Contents/MacOS/`. Editing source does nothing to that binary until you rebuild and reinstall. Three separate macOS mechanisms conspire to hide this:
- No automatic rebuild. A source edit is just a text change on disk. Nothing recompiles or reinstalls the bundle for you. If your build step is a one-off command you ran days ago, the installed binary is frozen at that point in time. The "live" app is literally old.
- LaunchServices caches the bundle. macOS keeps a Launch Services database describing every registered app — its identity, icon, and `Info.plist` metadata — and serves from that cache. It only refreshes a registration when it notices the bundle's modification date changed, which is why a stale icon or stale metadata can survive an update. The `lsregister` tool exposes a `-f` flag specifically to "force-update registration even if mod date is unchanged," and `-kill` to reset the database entirely (ss64.com — lsregister). If the OS is showing you a cached registration, you can swap the binary and still see the old behavior surfaced through Finder (Apple Developer Forums — icon not updating, cached?).
- Code signing seals the whole bundle. When you sign a `.app`, the signature seals over every resource in the bundle, not just the executable. If you drop a freshly built executable into an already-signed bundle, the signature no longer matches the contents, and macOS may refuse to launch it or kill it on launch — so you "fix" the binary and the app either won't start or silently runs the last version the system trusted (Apple Developer Forums — replacing binary without breaking signing; The Eclectic Light Company — how code signing seals a bundle). For ad-hoc/adhoc-signed apps this bites hardest: replacing just the executable leaves the bundle's seal inconsistent and you must re-sign the entire `.app`.
The deeper failure was process, not platform: we had no single, re-runnable build-and-install command. "How do I deploy this?" had a different ad-hoc answer every time, so it was easy to skip and easy to do wrong.
The fix
Make every app's build re-runnable and reinstall before you debug code. Concretely:
- Give each app one committed build script that does the whole pipeline: compile, copy the new bundle over `/Applications/YourApp.app`, and re-sign the whole bundle (not just the executable). One command, checked into the repo, runs identically every time. No remembering flags.
- Reinstall, then verify you're running the new binary before you reason about behavior. Quit the old app fully first (a running process keeps using the old executable). Confirm the on-disk binary actually changed — for example, compare its timestamp or hash, or have the app print a build stamp at launch — so "I reinstalled" is a fact, not an assumption.
- Re-sign the entire `.app`, not the lone executable. After swapping the binary, sign the bundle as a unit. If you only replace the executable inside an already-signed bundle, the seal is broken and launch behavior is undefined.
- Bust the LaunchServices cache when metadata or icons look stale. Force re-registration with `lsregister -f` on the bundle, or `lsregister -kill -r -domain local -domain user` to rebuild the database, then relaunch. This is for cached registration symptoms (old icon, old `Info.plist` values), and is independent of the binary swap above (ss64.com — lsregister).
- Only after the installed binary is provably current, debug the source. If the new binary is confirmed running and the bug persists, now the source is suspect — and now your reasoning is grounded.
Apply it
- Default diagnosis flips. When someone says "your fix did nothing," your first hypothesis is stale installed binary, not wrong source. Check the artifact the user actually runs before you touch code.
- Re-runnable beats remembered. Any deploy step that lives in your head or your shell history will eventually be skipped or done wrong. Commit it as a script.
- The running process pins the old binary. Always fully quit before reinstalling, or you test the previous build and conclude the fix failed.
- On macOS, signing is a bundle property, not a file property. Replacing one file inside a signed bundle invalidates the seal — re-sign the bundle.
- Separate two distinct staleness bugs. A stale binary (you never reinstalled) and a stale LaunchServices registration (the OS cached old metadata/icon) have different fixes — rebuild-and-reinstall vs. `lsregister`. Don't apply one and expect it to cure the other.