An ORM default does not change the database
Changing a default in your ORM schema changes your types, not your database; and a default that pins a fast-moving build tool is a product decision, not a version bump.
Read the lesson →Framework internals can break error handling
Importing a framework's internal helpers can silently break its error handling: an instanceof check fails when the class comes from a second bundled copy, and every typed error becomes a 500.
Map not-found to 404 around one lookup
Translate a database “not found” into a 404 only around the one query whose absence means “not found”. If the catch wraps a whole flow, a missing row in a later query gets reported as the wrong error.
Read the lesson →Copy a tool’s credential lookup order exactly
When you re-implement another tool's lookup rules (credentials, config files, env precedence), copy its order and its failure behaviour exactly, read from its source, or users get different credentials in your tool than in theirs.
Read the lesson →Sorting each batch is not a global order
Sorting inside each batch is not the same as ordering globally: if a priority has to win over a dependency graph, pick the next batch by priority first, then sort.
Read the lesson →A catch that only logs lets failures through
When you move error handling into an inner function, check the outer catch: if it only logs, the failure stops propagating and everything after it runs as if nothing happened.
A bare ?dryRun flag must mean on
Parse safety flags so that their presence means “on”. Then ?dryRun with no value does a preview and never writes.
Map API field names at the boundary
When the public API names differ from the internal ones, a straight pass-through can validate cleanly and still drop the data; map the names explicitly at the boundary and test that the write took effect, not just that the call succeeded.
Read the lesson →A skipped check is not a passed check
“Could not check” is not “checked and fine”: return a third state for skipped AI or validation calls so nothing caches it as a pass.
Read the lesson →Schemas that strip unknown keys hide bugs
A schema that strips unknown keys turns “forgot to add the field” into silent success; when you add a field to an API, check that the parse schema, not just the handler, knows about it.
Read the lesson →CI directory defaults skip action steps
CI defaults are narrower than they look: GitHub Actions' defaults.run.working-directory covers shell steps only, so every uses: step in a sub-directory job needs the directory passed explicitly.
A timestamp alone is not a paging cursor
A pagination cursor must be unique and self-contained: pair the timestamp with the row id, and store the values you compare against rather than a pointer to a row that might be deleted.
Read the lesson →Scope a kill switch to the feature it guards
A kill switch added to a shared validation path must be scoped to the feature it guards, or flipping it silently disables checks for unrelated data that shares the same code.
Read the lesson →Audit stored values before reusing a field
Before giving a stored field a new meaning, list every value it already holds in production and who wrote it; a side-effect '' or a legacy false will be read by the new code as a deliberate choice.
An LLM that rewrites a file must see all of it
When a model is asked to output a whole file that you then write to disk, it must have seen the whole file; truncating the input silently turns an “edit” into a deletion.
Read the lesson →Paging results merged from several queries
When one “page” is the merge of several paged queries, page every query with the same offset. “Is there more?” is true if any source has more. Paging only the main query silently drops the rest.
Read the lesson →enabled: false does not stop refetch()
enabled: false only pauses automatic fetching: a manual refetch() still fires, and old cached data stays visible. To make a query truly impossible, swap the query function for skipToken, and decide separately what cached data should show.
A read-only backup role is not one grant
A “read-only backup role” in Postgres is not one grant: pg_read_all_data skips large objects, table owners skip RLS unless it is forced, and --schema dumps silently leave large objects out.
Still loading is not the same as not found
When you swap a synchronous mock for a real API, every “if there's no data, treat it as missing” check becomes a bug, because “still loading” also has no data. Mocks that answer instantly hide this whole class.
Read the lesson →When 0 is valid, decide option precedence
When 0 is a valid value for an option, decide precedence explicitly (“if set, ignore the other option”) instead of patching truthiness checks one at a time.
Bulk actions must act on the whole selection
A bulk-selection UI must not offer a single-record action for a multi-record selection: the command either loops over every selected record or is hidden. And fix render churn where the unstable value is created, not in the shared hook downstream.
Read the lesson →