The agent was asked to tighten a loose input handler, so it marked an optional field non-empty in the parser and added a clear error when the value was blank. The new test sent the field with a real value, the suite passed, and the change looked like defensive cleanup. Within a day an older client started failing every request, because that client had always sent the optional as an empty string rather than omitting it.

The bug was not the rule itself. It was the assumption behind it: that optional means "key absent," when a legacy client treats optional as "key present but empty," and the two are different shapes the parser collapsed into one.

Three shapes that look identical to a happy-path test

ShapeWhat the client meansWhat a strict check usually does
Key omitted entirelyField not applicableAccept; treat as unset
Key sent as nullExplicitly no valueAccept as unset
Key sent as empty string ""Unfilled but still transmittedReject as blank

A synthetic example built by the agent uses the first row, so the suite never exercises the third. The old client was written against a permissive parser that accepted every row, and nobody logged which rows actually arrived in traffic. Tightening to one row quietly narrowed the accepted set without a test or a deprecation window to notice.

Why the suite stayed green

The agent tests the field it chose to include. It rarely replays real payloads that predate the current client, so empty-string and null variants never appear in the fixtures. The rule looks correct against the shape the author imagined, and the error message is even helpful, which makes the rejection feel like a win rather than a regression.

Real traffic has been drifted into accepted shapes over years of clients, gateways, and hand-written scripts. Each one may encode a slightly different convention for "nothing here." A single non-empty check assumes the newest client's habit is the universal one.

Separate "missing" from "empty" before rejecting

The safe move is to decide each shape on its own, not flatten them:

  • Omitted or null: treat as unset and accept, matching the old permissive behavior.
  • Empty string: normalize to unset rather than reject, unless the field genuinely requires content on this endpoint.
  • Blank after trimming: normalize to unset in the same pass, so spacing differences do not become failures.
  • A field that must carry a value should be documented as required and announced with a deprecation window, not quietly enforced by a cleanup pass.

Capture a short sample of recent payloads for the field before tightening: count how often it is omitted, null, empty, or filled. That distribution tells you which shapes live in production instead of guessing from one example.

Review the rule against the oldest client, not the newest

When validation gets stricter, the question a reviewer should ask is which existing caller this breaks, not whether the rule reads sensibly on a blank example. Ask the agent to list accepted input variants and to write a fixture for each that legacy traffic used, then normalize the ambiguous empty cases into unset before deciding whether rejection is warranted.

Cleanup that narrows what the system accepts is always riskier than cleanup that only adds checks. A parser that used to be permissive carries an unwritten contract with every client still sending the old shape; a test written from scratch cannot reconstruct that contract.

How does your team tell apart a field that is absent from one that arrived empty, and do your fixtures include the old client's actual shape?