Found while implementing #13239 (object-level declared indexes[] uniqueness in driver-memory). Not folded into that PR — different defect class, and it predates both uniqueness cards. Unassigned; grading and routing are triage's.
Triage routing, stated up front
#5499 (maintainer 2026-08-05) freezes defect-fix investment in driver-memory, with a standing rule that new cards in this family carry domain:engine and go straight to pm:on-hold referencing it rather than into pm:queue. This card is filed that way. It is recorded, not queued.
Whether it reaches the ruling's exception channel — a driver-memory semantics error that makes CI green wrongly — is triage's call, and the argument is weak here: bulkCreate is not used as a uniqueness oracle by any suite this card found, so the false-green exposure is smaller than the one #13197 / #13239 were escalated on.
The observation
InMemoryDriver.bulkCreate is one line:
constresults=awaitPromise.all(dataArray.map(data=>this.create(object,data,options)));
create writes into the table synchronously, so the batch is not atomic: when any row in it is refused, every row accepted BEFORE the refusal stays in the store, and the caller gets a rejection describing a batch that partly landed. Measured while writing #13239's suite, on a two-row batch whose second row collides with its first:
before: 2 rows
bulkCreate([A9/Z, A9/Z]) -> rejects with UNIQUE_VIOLATION / 409
after: 3 rows (the FIRST batch row landed; the duplicate did not)
The refusal itself is correct — the duplicate is refused, which is what #13197 and #13239 pin. What is wrong is the surviving prefix.
Why it is worth recording
It is older than uniqueness: any mid-batch create failure has always left a partial batch, so a caller's retry of the same array is unsafe on this driver for reasons that have nothing to do with constraints. And it is a divergence from the family this driver stands in for — driver-sql issues a batch insert inside one statement, so a constraint failure there leaves the table untouched. updateMany on this same driver already takes the opposite, correct posture: #13197 made it prepare and check every row before mutating any of it, precisely so a half-applied batch cannot happen. bulkCreate never got that treatment, so the two batch paths of one driver disagree about whether a batch is all-or-nothing.
The shape a fix would take is updateMany's, one method over: build every stored record, check the whole set (against the table and against each other), then push. create's own single-row path is unchanged by that.
Where it is
packages/drivers/driver-memory/src/memory-driver.ts — bulkCreate, with updateMany in the same file as the pattern to copy.
Not claimed here
No position on whether it should be fixed. This driver is documented as a deliberately WEAK oracle, and "batches are not atomic here, use SQLite when that matters" is a legitimate answer — it is simply not the answer anywhere in the tree states today. #13239's suite records the current behaviour explicitly rather than silently, so nothing is asserting the atomic reading.
Related
#5499 (the investment freeze this is filed under) - #13197 / #13239 (the uniqueness cards that surfaced it; updateMany's check-before-mutate came from the first)
Generated by Claude Code
Generated by Claude Code
Found while implementing #13239 (object-level declared
indexes[]uniqueness indriver-memory). Not folded into that PR — different defect class, and it predates both uniqueness cards. Unassigned; grading and routing are triage's.Triage routing, stated up front
#5499 (maintainer 2026-08-05) freezes defect-fix investment in
driver-memory, with a standing rule that new cards in this family carrydomain:engineand go straight topm:on-holdreferencing it rather than intopm:queue. This card is filed that way. It is recorded, not queued.Whether it reaches the ruling's exception channel — a
driver-memorysemantics error that makes CI green wrongly — is triage's call, and the argument is weak here:bulkCreateis not used as a uniqueness oracle by any suite this card found, so the false-green exposure is smaller than the one #13197 / #13239 were escalated on.The observation
InMemoryDriver.bulkCreateis one line:createwrites into the table synchronously, so the batch is not atomic: when any row in it is refused, every row accepted BEFORE the refusal stays in the store, and the caller gets a rejection describing a batch that partly landed. Measured while writing #13239's suite, on a two-row batch whose second row collides with its first:The refusal itself is correct — the duplicate is refused, which is what #13197 and #13239 pin. What is wrong is the surviving prefix.
Why it is worth recording
It is older than uniqueness: any mid-batch
createfailure has always left a partial batch, so a caller's retry of the same array is unsafe on this driver for reasons that have nothing to do with constraints. And it is a divergence from the family this driver stands in for —driver-sqlissues a batch insert inside one statement, so a constraint failure there leaves the table untouched.updateManyon this same driver already takes the opposite, correct posture: #13197 made it prepare and check every row before mutating any of it, precisely so a half-applied batch cannot happen.bulkCreatenever got that treatment, so the two batch paths of one driver disagree about whether a batch is all-or-nothing.The shape a fix would take is
updateMany's, one method over: build every stored record, check the whole set (against the table and against each other), then push.create's own single-row path is unchanged by that.Where it is
packages/drivers/driver-memory/src/memory-driver.ts—bulkCreate, withupdateManyin the same file as the pattern to copy.Not claimed here
No position on whether it should be fixed. This driver is documented as a deliberately WEAK oracle, and "batches are not atomic here, use SQLite when that matters" is a legitimate answer — it is simply not the answer anywhere in the tree states today. #13239's suite records the current behaviour explicitly rather than silently, so nothing is asserting the atomic reading.
Related
#5499 (the investment freeze this is filed under) - #13197 / #13239 (the uniqueness cards that surfaced it;
updateMany's check-before-mutate came from the first)Generated by Claude Code
Generated by Claude Code