Skip to content

Commit 39f36b4

Browse files
committed
fix(resume): guard patch versions in the transaction, not in sql
Postgres defaultNow() stores microseconds while JS Dates are millisecond-truncated, so the SQL equality guard matched zero rows on freshly created resumes and every guarded agent patch failed with a permanent version conflict. The SELECT ... FOR UPDATE lock plus the in-transaction ms-precision check already provide the guarantee; drop the SQL predicate. Verified A/B against a live database.
1 parent 39590ea commit 39f36b4

1 file changed

Lines changed: 5 additions & 6 deletions

File tree

packages/api/src/features/resume/service.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,15 @@ async function applyResumePatchTx(
160160
}
161161

162162
patchedData = parseWritableResumeData(patchedData);
163+
// The version guard is the ms-precision JS check above, under the SELECT ... FOR UPDATE lock.
164+
// Never compare expectedUpdatedAt in SQL: rows stamped by Postgres now() (defaultNow() on
165+
// insert) carry microseconds, while JS Dates are ms-truncated — SQL equality then matches
166+
// zero rows and every guarded patch on a fresh resume reports a version conflict forever.
163167
const [resume] = await client
164168
.update(schema.resume)
165169
.set({ data: patchedData })
166170
.where(
167-
and(
168-
eq(schema.resume.id, input.id),
169-
eq(schema.resume.isLocked, false),
170-
eq(schema.resume.userId, input.userId),
171-
...(input.expectedUpdatedAt ? [eq(schema.resume.updatedAt, input.expectedUpdatedAt)] : []),
172-
),
171+
and(eq(schema.resume.id, input.id), eq(schema.resume.isLocked, false), eq(schema.resume.userId, input.userId)),
173172
)
174173
.returning({
175174
id: schema.resume.id,

0 commit comments

Comments
 (0)