How Silently Ignored Config Keys Slip Through CI, and Where to Block Them
A key you added to a config file can be discarded without a single warning. Measured behavior across tsc, ESLint, wrangler, npm and vitest, plus a probe that verifies a setting is actually in effect and a wrapper that turns warnings into build failures.
A Cloudflare Workers deploy stopped one afternoon with "Missing entry-point to Worker script." Opening wrangler.toml showed main sitting right there. Correct spelling. Valid path. Still no deploy.
The problem was not how the key was written, but where. A [build] section had been added earlier in the file, and main came after it, which means TOML reads main as a member of the build table. Correct to a human eye, something else entirely to the parser.
The error message, meanwhile, politely suggested adding main to the file. Being told to add something that is already there is a special kind of detour.
As an indie developer, an afternoon spent that way stings. What stayed with me afterwards was not the fix, though. It was the realization that the same mismatch usually happens far more quietly.
Reproducing the failure where placement changes meaning
Here is the minimal version, confirmed against wrangler 4.125.0.
▲ [WARNING] Processing wrangler.order.toml configuration:
- Unexpected fields found in build field: "main","compatibility_flags"
✘ [ERROR] Missing entry-point to Worker script or to assets directory
The warning names the actual culprit. The red ERROR pulls your attention away from it, and the investigation starts at the entry point path instead. That is exactly where I started.
The fix is trivial: move top-level fields above [build]. What lingered was the broader observation that a config file can be read differently from how it was written, and nothing is obliged to tell you.
Bad config keys fall into three tiers
To check that observation, I introduced the same class of mistake into the tools I use most and recorded what each one did. Everything below ran on Linux with Node 22.23.2 on 2026-08-25.
Tool
Mistake introduced
Reaction
Exit code
TypeScript 5.9.3
strictNullCheck (should be strictNullChecks)
Stops with TS5025 and suggests the correct key
2
TypeScript 5.9.3
include placed inside compilerOptions
Stops with TS5023
2
ESLint 9.39.5 (flat config)
Two unknown keys added
Aborts immediately with ConfigError
2
Wrangler 4.125.0
compatability_flags (misspelled)
Prints a WARNING and continues
0
npm 10.9.8
depedencies (misspelled)
Silent. Reports "up to date" and installs nothing
0
Vitest 3.2.7
Three invalid keys at once
Silent. Every test passes
0
One transposed character, and the tools split cleanly into ones that stop you and ones that wave you through.
The npm case is the one that bothers me most. With dependencies misspelled as depedencies, npm install finished in 327ms, printed "up to date", returned exit code 0, and created no node_modules at all. A project with zero dependencies installed is recorded as a successful install.
Misspell scripts as scripst in the same file and the story changes: npm run build fails instantly with "Missing script: build". Keys that something later calls will speak up. Keys that merely declare things stay quiet.
✦
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
✦You will be able to verify that a config change an agent made is actually in effect, by observing behavior instead of re-reading the diff
✦You will be able to drop a few lines of gating into your own repository that stop a silently discarded config change before it reaches production
✦You will be able to tell which of your tools fail loudly, warn quietly, or say nothing at all, and narrow your probes down to the few places that need them
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 silent tier drags your green test run along with it
I pushed on the Vitest result a little further. This config contains three separate mistakes:
import { defineConfig } from 'vitest/config';export default defineConfig({ test: { include: ['t/**/*.test.ts'], timeout: 10, // the real key is testTimeout retires: 3, // the real key is retry coverageThreshold: 95, // no such key },});
The test under it does nothing but sleep for 800ms. If timeout: 10 were in effect, it would obviously fail.
✓ t/a.test.ts (1 test) 804ms
Test Files 1 passed (1)
It passed, after 804ms, without a single warning. Rename the key to testTimeout: 10 and the same test fails with Test timed out in 10ms.
Which means the config file produced identical behavior before and after the change. The diff lands, review approves it, CI stays green, and nothing actually changed. A repository that believes it has a time limit keeps running without one.
Timeouts belong to the category of settings that do nothing on a normal day. The only occasion that reveals a broken timeout is the day something runs away with itself, which is precisely the wrong moment to find out.
Why this gap widens once agents edit your config
Picture asking an Antigravity agent to set the test timeout to ten seconds. It opens the config, adds a plausible key, shows you the diff, and reports completion.
The report is not false. The file really did change. Nothing guarantees the runtime will read that key, though. If the agent leaned on documentation for an older release, or blended in a neighboring tool's key name, a silent-tier tool accepts the value and drops it without comment.
The difference from hand-editing is volume and pace. Once you are touching twenty config values in an afternoon, the absolute number of "written but not in effect" spots grows, and the silent tier offers no signal that it grew.
The useful shift here is not to distrust agent output. It is to stop verifying the contents of config files and start verifying the effect of the configuration. File contents are only a proxy for runtime behavior.
The technique is unglamorous: write a test that must fail if the setting is in effect, then confirm that it does fail.
// probe.test.ts — must fail whenever testTimeout is liveimport { it } from 'vitest';it('config probe: must time out', async () => { await new Promise((r) => setTimeout(r, 5000));});
// vitest.probe.config.ts — uses the same key as the real configimport { defineConfig } from 'vitest/config';export default defineConfig({ test: { include: ['probe.test.ts'], testTimeout: 10 },});
On the calling side, success and failure swap meanings:
#!/usr/bin/env bash# For a probe, failing is the healthy outcome. Passing means the setting is dead.npx vitest run --config vitest.probe.config.ts >/tmp/probe.log 2>&1if [ $? -eq 0 ]; then echo "config-probe: testTimeout is not in effect (the probe passed)" >&2 exit 1fiecho "config-probe: ok"
Running this locally, the correct key made the probe exit 1, and reverting to the misspelled timeout made it exit 0. Whether the setting is live becomes a number you can branch on.
Probes earn their keep on safety devices that never fire during normal operation: timeouts, retry ceilings, concurrency caps, coverage floors, request size limits. From the outside, "not in effect" and "simply not triggered yet" look identical.
Settings used on every run need no probe at all. Get an output directory wrong and you notice the moment no artifacts appear. There is no point posting a guard on a key that already shouts.
Promote warnings into failures
Tools like wrangler are one step away from being useful here. The warning exists, but the exit code stays 0, so it drowns in CI output. No one reliably spots one yellow line in a forty-line build log, every time, forever.
Against the misspelled config it exits 1; against the config a production site actually deploys with, it exits 0 and reports no unknown fields. Getting a clean result on the live file was a pleasant side effect of writing the check.
Two caveats. First, warning text changes between releases, and this wrapper matches on a string, so right after any upgrade you should break a config on purpose once and confirm the check still catches it. Second, without set -o pipefail, a failure inside the pipeline gets swallowed, and a guard that goes silent is worse than no guard.
Deciding how far to take it
Probing every setting is neither practical nor free, and test runs only get longer. My ordering is:
Pick the settings that fail quietly: safety devices, ceilings, cutoffs
Of those, keep the ones where failure is expensive: unbounded processing, anything touching billing, destructive operations
Add one probe each. In most repositories that lands somewhere between two and four
Keeping the count low is about sustainability. A probe can also fail when the underlying setting is changed legitimately. With ten of them, something complains every time you touch a config, and eventually nobody reads the output. The number of guards is capped by the number of guards you can maintain.
Deciding not to probe matters just as much. Keys that already shout, keys used on every run, and keys that change once a year can stay off the list.
Run this once against your own setup
If you do one thing, find out which tier each of your everyday tools sits in. Change a single character in a config key and run your usual command. Does it stop, warn, or sail through? Five minutes, at most.
Any tool that sailed through marks either a place agents should not be editing unsupervised, or a place that needs a probe. Since running that check, the way I phrase requests has changed: not "add this setting," but "add this setting and a test that demonstrates it is in effect."
Whether a setting you believe you wrote is actually live has no answer inside the file itself. It is worth finding out on a quiet day, rather than on the day it matters.
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.