We Committed the Generated Types to the Repo; CI Stayed Green While the Server Dropped the Field
A customer reported that a profile screen showed a blank subscription tier. The SDK compiled, unit tests passed, CI was green on the branch that shipped. At runtime the code read profile.plan and got undefined , because the server had stopped returning that field two releases earlier. We had committed the generated TypeScript types straight into the repository. Nobody regenerated them, so the…
A customer reported a blank subscription tier on a profile screen. Despite successful compilation, unit tests, and a green CI run on the affected branch, runtime revealed the issue. The server had ceased returning a required field two releases prior. The problem arose because the SDK's generated TypeScript types had been committed to the repository without regeneration. This caused the code to read from a non-existent field, resulting in undefined values appearing in production traffic.
When generated types are committed, they cease being automatically updated. They become a static source file edited manually during code reviews, and any discrepancies between the committed snapshot and the live API go unnoticed. The assumption behind this approach is that the types remain stable, the differences are easy to review, and developers don't need the generator tool installed.
Typechecking only verifies that code matches a frozen contract, not the actual server response. TypeScript validates that code reads 'plan' against a type declaring 'plan?: Plan', but it doesn't communicate with the server. Both an optional field present in the outdated snapshot and a missing field from the live response typecheck identically. Without a guard or schema validation, a missing value seamlessly passes through to the UI, rendering an empty label.
The drift of outdated types is subtle. It doesn't cause build failures, test failures, or lint warnings; it merely returns undefined values in production. The only sign of a problem is an undefined value appearing in the UI. To detect such issues before merge, developers must regenerate types from the live spec as part of the build process, ensuring committed types reflect the current state of the API.
To prevent such drift, it's crucial to pin the spec version the SDK targets. Recording which commit or tagged release the generator consumes helps identify mismatches between checked-in types and live API. Replay recorded requests against the spec to capture real request/response pairs, revealing any missing fields that would otherwise go undetected. Validating optional fields at the boundary is also essential, as even when types allow optional values, the wire may not always provide them.
Ultimately, the cleanest approach is to treat generated types as a snapshot of the contract, not the source of truth. Before merge, replay recorded traffic against the local spec to spot any drift in the diff. This local-first loop ensures that drift is visible to reviewers, enabling early detection and correction of issues before they reach production.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.