Agent Config Drifts Quietly Across Environments: Detection and Correction
Across two Macs and an automation host, agent settings slowly diverge and only one side fails. Here is how to surface that config drift with normalized hashing and a correction workflow, from an indie developer's setup.
It's the same agent, yet it fails in only one environment. Comparing logs, the input is identical and so is the code. The cause was a setting I'd changed on one Mac weeks earlier that never propagated to the other. As an indie developer I work across a primary Mac, a secondary Mac, and an automation host, so I've been caught many times by settings that have "drifted apart at some point."
Config drift doesn't break all at once; it diverges a little at a time. That's exactly why it's hard to spot, and by the time you notice, you can't even tell which side is correct. The starting point is to stop eyeballing config and let a machine reconcile it.
Why drift creeps in silently
The classic source of divergence is emergency response. Late at night you fix a setting on one machine to get past a problem, intend to roll it out everywhere later, and forget. Or a tool update quietly changes a default and only one side picks up the new value. Tools like Antigravity, with desktop, CLI, and SDK surfaces, store settings in different places per surface, which widens the cracks drift can slip through.
The tricky part is that most drift sits harmless and ignored. Ninety-nine runs do nothing, and only the hundredth surfaces it as "one side's model setting is stale and the output format differs." The longer the harmless stretch, the harder it is to suspect a recent change, and the more your investigation wanders.
What counts as the source of truth
The first thing to decide is where the source of truth lives. I keep config in one place in the repo, like config/agent.toml, and each environment reads from it. With the source of truth in the repo, diffs live in git history, so "when, who, and what changed" is traceable later.
In reality, though, environment-specific values (local paths, the run host name) can't go in the source of truth. So I split config into two layers.
Layer
Examples
Reconciled?
Shared config
Model name, timeout, retry cap, gate thresholds
Yes (should match everywhere)
Environment-specific
Working directory, host name, parallelism
No (may differ per environment)
Narrowing reconciliation to shared config matters. Demand a match on environment-specific values too and you'll get "diff found" every time, hollowing out the warning.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A 25-line implementation that normalizes and hashes config to detect cross-environment drift in one command
✦A three-tier model (harmless / warn / fix now) for what to auto-correct versus what a human reviews
✦The weekly reconciliation that cut drift-driven overnight failures across two Macs plus an automation host
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The core is simple: normalize the shared config, hash it, and compare across environments. Normalization is there so that mere differences in key order or indentation don't get flagged as different. Parse the TOML or JSON once, sort the keys, write it back out, then hash.
import hashlib, json, tomllibSHARED_KEYS = {"model", "timeout_s", "max_retries", "gate_thresholds"}def shared_fingerprint(config_path: str) -> str: with open(config_path, "rb") as f: data = tomllib.load(f) # Pull only shared keys, normalize order, then hash shared = {k: data[k] for k in sorted(SHARED_KEYS) if k in data} canonical = json.dumps(shared, sort_keys=True, ensure_ascii=False) return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:16]
Print this fingerprint in each environment and compare it to the source of truth's. Match means shared config is aligned. On mismatch, show exactly which keys diverged, as below. The hash quickly decides "is it drifted," and the diff shows "where it drifted" — a clean division of labor.
def diff_shared(a_path: str, b_path: str) -> list[str]: def load(p): with open(p, "rb") as f: d = tomllib.load(f) return {k: d.get(k) for k in SHARED_KEYS} a, b = load(a_path), load(b_path) return [f"{k}: {a[k]!r} != {b[k]!r}" for k in SHARED_KEYS if a[k] != b[k]]
Handle drift in three tiers
Detecting it doesn't mean every case should be fixed immediately. I handle drift in three tiers.
First is harmless drift. Environment-specific differences like parallelism or local paths are excluded from reconciliation, so they never appear here. If one does appear, it's a sign your split is wrong, so revisit SHARED_KEYS.
Second is warn drift. Values like timeout or retry cap — not fatal when diverged, but eventually one side behaves differently. On detection, raise a warning and align them during your next bit of work. Don't auto-fix; let a human decide which way to align.
Third is fix-now drift. Values like model name or gate thresholds, which tie directly to output quality or safety. Halt the job the moment it's detected and force-align it to the source of truth. In my operation, I once found one environment with a drifted gate threshold, and an article that had skipped the quality check was about to ship — ever since, this tier alone is auto-corrected.
CRITICAL = {"model", "gate_thresholds"}def decide(diff_keys: set[str]) -> str: if diff_keys & CRITICAL: return "halt_and_fix" # fix now: stop the job, align to source of truth if diff_keys: return "warn" # warn: a human aligns it return "ok"
Build a weekly reconciliation into operations
Detection machinery earns its keep only when run on a cadence. Once a week, in a quiet Sunday slot, I run a reconciliation that checks every environment's fingerprint against the source of truth. No diff prints a single "match" line, so the check takes seconds.
One trick for timing: right after a big tool update, I run an extra reconciliation. Tools like Antigravity, whose versions move briskly, can change defaults on update, and that's exactly when drift slips in. Adding "right after an update" to the regular weekly cadence catches default-change divergence far sooner.
It's far easier to set up the ability to notice drift than to fix it after the fact. Fix the source of truth in one place, reconcile only shared config by hash, and auto-correct only the high-impact values — this three-part stance makes agents across environments remarkably quiet. Start by printing and comparing the shared-key fingerprint across your two environments. I hope it helps anyone puzzling over the same "only one side fails" mystery.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.