Skip to content

data: updateMany runs hooks for a nonexistent id — the row fails INTERNAL_ERROR from a hook-condition abort instead of RECORD_NOT_FOUND #5088

Description

@baozhoutao

Found working the #3909 G3/G4 checklist on main @ a1a855a28 (the rc.3 train tip), driving the showcase.

updateMany runs record-change hooks for an id that does not exist. With no stored record to merge, the hook's condition evaluates against a payload-only record, the field it reads is absent, and #4775's new abort fires — so the row fails with INTERNAL_ERROR from a hook diagnostic instead of RECORD_NOT_FOUND.

Repro

The single-record path is correct (this is the #4435 fix, verified separately):

PATCH /api/v1/data/showcase_task/definitely_missing {"progress":1}
→ 404 {"code":"RECORD_NOT_FOUND","error":"Record definitely_missing not found in showcase_task"}

The batch path is not:

POST /api/v1/data/showcase_task/updateMany
{"records":[{"id":"definitely_missing","data":{"progress":1}}]}
→ 200, row 0 failed:
{"code":"INTERNAL_ERROR",
"message":"Hook 'showcase_audit_task_completion' could not evaluate its condition
(runtime: No such key: done) — operation aborted. The condition reads 'done',
which this object does not declare — fix the hook's condition, or declare the field."}

showcase_taskdoes declare done; the hook is correct. The message is wrong because the record it evaluated against was the patch payload alone.

With atomic: true the same shape poisons the whole batch — row 0 fails this way and every later row comes back NOT_ATTEMPTED:

{"succeeded":0,"failed":2,"codes":["INTERNAL_ERROR","NOT_ATTEMPTED"]}

Real ids behave correctly on the same route ({"succeeded":2,"failed":0}), so this is specific to the missing-record case.

Why it matters

Three separate contracts this train shipped disagree here:

The practical harm is the misdirection: an operator who batch-updates with one stale id is told their hook is broken and pointed at the object's field list. Under atomic they also lose every other row in the batch to NOT_ATTEMPTED, so a single stale id in a large batch reads as a platform failure.

Expected

updateMany resolves each row's stored record before running hooks, and a row whose id resolves to nothing fails with RECORD_NOT_FOUND — matching the single-record path — rather than entering the hook pipeline with a payload-only record. If a hook must run for a missing record, the merged record should be recognisable as incomplete so #4775 does not attribute the gap to the hook's author.

Found during the #3909 rc.3 verification (G3/G4).

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions