fix(library-identity-consumer): stringify lastVerifiedAt to defeat Drizzle's date-serializer override#976
Merged
Conversation
…izzle's date-serializer override The first prod run of library-identity-consumer on 2026-05-20 had every UPSERT (14,405/14,405) fail at the writer with no library_identity rows landing. Diagnosis traced the failure to Drizzle's drizzle() factory in node_modules/drizzle-orm/postgres-js/driver.js:19, which overrides postgres-js's default date serializer (OIDs 1184/1082/1083/1114/1182/1185/1115/1231) with a transparent passthrough. The override cites porsager/postgres#761 and is intended to defeat postgres-js's *inbound* date parser (so reads return raw text for Drizzle to convert itself). But the same loop also rebinds the *outbound* serializer — a JS Date passed via `${...}` in a `sql\`\`` template arrives at postgres-js's Bind() as a Date, the transparent serializer returns it unchanged, and Buffer.byteLength() inside b.str() throws ERR_INVALID_ARG_TYPE. Other backfill jobs in this repo (library-canonical-entity-backfill, etc.) sidestep the bug by using NOW() in SQL rather than passing a JS Date as a parameter; this writer was the only one passing a Date through the template. Pre-stringifying with .toISOString() defeats the override (the resulting string passes through the transparent serializer unchanged) and gives PG a parseable timestamptz literal. End-to-end verified against prod with the exact writer code path: both library_identity_source and library_identity INSERTs now succeed.
Member
Author
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First prod run of
library-identity-consumeron 2026-05-20 had 14,405 / 14,405 UPSERTs fail with zerolibrary_identityrows landing. LML resolution was healthy (matched dry-run exactly); the failure was 100% writer-side.Root cause is a Drizzle / postgres-js interaction documented at porsager/postgres#761 and present in node_modules/drizzle-orm/postgres-js/driver.js:19:
The override is intended for inbound parsers (so reads return raw text for Drizzle to convert itself), but the same loop also rebinds the outbound serializer for OID 1184 (timestamptz). The native postgres-js serializer would call
.toISOString()on Date objects; the transparent passthrough returns the Date unchanged. Then postgres-js'sBind()→b.str(date)→Buffer.byteLength(date)→ERR_INVALID_ARG_TYPE: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received an instance of Date.Why no other job hit this
library-canonical-entity-backfill,flowsheet-metadata-backfill,library-artwork-url-backfill, andflowsheet-dj-name-backfillall sidestep the bug by either usingnow()in SQL or by going throughdb.insert(...).values({...})(Drizzle's column-aware encoder path, which doesn't hit the transparent serializer). The consumer'swriteSingleArtistis the only writer in the repo that passes a JSDateas${value}inside asql\`` raw template.The fix
new Date()→new Date().toISOString(). A pre-stringified ISO 8601 string passes through the transparent serializer unchanged, and PG parses the literal as atimestamptzhappily.Diff is +11/-1 in a single file, with a long comment block explaining the citation so future readers don't need to re-walk this investigation.
Test plan
drizzle()+tx.execute(sql\`)` code path as the writer — fails with the documented ERR_INVALID_ARG_TYPE.library_identity_sourceandlibrary_identityUPSERTs land cleanly whenlastVerifiedAtis pre-stringified.npx tsc --noEmit -p jobs/library-identity-consumer/tsconfig.jsonclean.Deploy + re-run
After this lands:
Manual Build & Deploy target=library-identity-consumer.docker pull 203767826763.dkr.ecr.us-east-1.amazonaws.com/library-identity-consumer:<new tag>.DRY_RUN) — expected: 14,405 `rows_resolved`, ~30K source rows, 15,376 `rows_unresolved`, 0 `writer_error`.library_identitypopulated for the 14,405 resolved rows.Recommend also shipping the companion observability fix (PR #975) so future writer failures actually surface the PG code/constraint in logs instead of just "Failed query".
Related