{"record":{"id":"3be3b35ae4ff6622","repo":"tldraw/tldraw","slug":"bad-request","errorCode":"bad_request","errorMessage":"update failed, no matching rows","messagePattern":"update failed, no matching rows","errorType":"error_code","errorClass":"ZMutationError","httpStatus":null,"severity":"error","filePath":"apps/dotcom/sync-worker/src/zero/ServerCrud.ts","lineNumber":178,"sourceCode":"\t}\n\n\tasync update(data: any) {\n\t\tassert(!this.signal.aborted, 'CRUD usage outside of mutator scope')\n\t\tassertRowIsValid(data, this.table)\n\t\tconst vals = omit(data, this.table.primaryKey)\n\t\tif (Object.keys(vals).length === 0) {\n\t\t\tconsole.error('update is a noop', data)\n\t\t\treturn\n\t\t}\n\n\t\tconst res = await this._exec(\n\t\t\tthis._wherePrimaryKey(db.updateTable(this.table.name).set(vals), data)\n\t\t)\n\t\tif (res.rowCount !== 1) {\n\t\t\t// might have been a noop\n\t\t\tconst doesExist = await this._doesExist(data)\n\t\t\tif (!doesExist) {\n\t\t\t\tthrow new ZMutationError(ZErrorCode.bad_request, `update failed, no matching rows`)\n\t\t\t}\n\t\t}\n\t\tawait this._trackUpdated(data)\n\t}\n}\n\nfunction assertRowIsValid(row: any, table: TlaSchema['tables'][keyof TlaSchema['tables']]): void {\n\tconst entries = Object.entries(row)\n\tfor (const [key, value] of entries) {\n\t\tconst column = table.columns[key as keyof typeof table.columns] as SchemaValue\n\t\tif (!column) {\n\t\t\tthrow new Error(`Unknown column ${key} in table ${table.name}`)\n\t\t}\n\t\tif (value == null && column.optional) {\n\t\t\tcontinue\n\t\t}\n\t\tswitch (column.type) {\n\t\t\tcase 'string':","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/tldraw/tldraw/blob/b31086b44731a7d1d9d46be4163ac8ef7417321d/apps/dotcom/sync-worker/src/zero/ServerCrud.ts#L160-L196","documentation":"Thrown as ZMutationError(ZErrorCode.bad_request) inside ServerCRUD.update when a Kysely UPDATE affected a rowCount other than 1 and a follow-up _doesExist check confirms the row does not exist. Zero's mutate pipeline calls update with the assumption the row is present; a missing row is reported as a bad_request rather than a silent no-op, so the client learns the entity it is patching is gone.","triggerScenarios":"A zero-cache client pushing a mutate with mutators that call table.update() for a primary key that was never inserted or was concurrently deleted. The check fires only after the UPDATE returns rowCount !== 1 AND _doesExist returns false (rowCount 1 from a real no-op or a successful update does not throw).","commonSituations":"A client optimistically updating a row that the server never received an insert for; a race where another client deleted the row between the client's last sync and its mutate; an off-by-one or stale id in the mutator payload.","solutions":["Use upsert instead of update when the row may not yet exist — upsert inserts on conflict.","Ensure the corresponding insert mutation ran (and was acknowledged) before issuing updates against the same primary key.","Handle bad_request on the client by re-syncing from the server and re-applying the mutation against the current state."],"exampleFix":"// before\nmutators.updateFileState({ id, ...patch })  // throws if 'id' never inserted\n// after\nmutators.upsertFileState({ id, ...patch })   // inserts or updates","handlingStrategy":"try-catch","validationCode":"// In a Zero mutator, prefer upsert when the row may not exist:\nm Table.upsert({ ... })  // instead of Table.update({ ... })","typeGuard":null,"tryCatchPattern":"try {\n  await mutators.updateRow(...)\n} catch (e) {\n  if (e instanceof ZMutationError && e.errorCode === ZErrorCode.bad_request) {\n    // row is gone: re-sync and re-apply, or switch to upsert\n  } else throw e\n}","preventionTips":["Use upsert when the row's existence is not guaranteed.","Ensure the insert mutation is acknowledged before issuing updates.","On bad_request from update, re-sync from the server to reconcile."],"tags":["zero","database","crud","mutation","bad-request"],"backgroundTag":null,"analyzedSha":"b31086b44731a7d1d9d46be4163ac8ef7417321d","analyzedAt":"2026-08-12T17:05:39.947Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}