{"record":{"id":"7bef75bfbb55369f","repo":"n8n-io/n8n","slug":"the-optimistic-lock-on-entity-entity-failed-ve","errorCode":null,"errorMessage":"The optimistic lock on entity ${entity} failed, version ${expectedVersion} was expected, but is actually ${actualVersion}.","messagePattern":"The optimistic lock on entity (.+?) failed, version (.+?) was expected, but is actually (.+?)\\.","errorType":"exception","errorClass":"OptimisticLockVersionMismatchError","httpStatus":null,"severity":"error","filePath":"packages/@n8n/typeorm/src/query-builder/SelectQueryBuilder.ts","lineNumber":1564,"sourceCode":"\t\t\t\t// means we created our own query runner\n\t\t\t\tawait queryRunner.release();\n\t\t}\n\t}\n\n\t/**\n\t * Gets single entity returned by execution of generated query builder sql.\n\t */\n\tasync getOne(): Promise<Entity | null> {\n\t\tconst results = await this.getRawAndEntities();\n\t\tconst result = results.entities[0] as any;\n\n\t\tif (result && this.expressionMap.lockMode === 'optimistic' && this.expressionMap.lockVersion) {\n\t\t\tconst metadata = this.expressionMap.mainAlias!.metadata;\n\n\t\t\tif (this.expressionMap.lockVersion instanceof Date) {\n\t\t\t\tconst actualVersion = metadata.updateDateColumn!.getEntityValue(result); // what if columns arent set?\n\t\t\t\tif (actualVersion.getTime() !== this.expressionMap.lockVersion.getTime())\n\t\t\t\t\tthrow new OptimisticLockVersionMismatchError(\n\t\t\t\t\t\tmetadata.name,\n\t\t\t\t\t\tthis.expressionMap.lockVersion,\n\t\t\t\t\t\tactualVersion,\n\t\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tconst actualVersion = metadata.versionColumn!.getEntityValue(result); // what if columns arent set?\n\t\t\t\tif (actualVersion !== this.expressionMap.lockVersion)\n\t\t\t\t\tthrow new OptimisticLockVersionMismatchError(\n\t\t\t\t\t\tmetadata.name,\n\t\t\t\t\t\tthis.expressionMap.lockVersion,\n\t\t\t\t\t\tactualVersion,\n\t\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (result === undefined) {\n\t\t\treturn null;\n\t\t}","sourceCodeStart":1546,"sourceCodeEnd":1582,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/typeorm/src/query-builder/SelectQueryBuilder.ts#L1546-L1582","documentation":"Thrown as OptimisticLockVersionMismatchError from getOne() when lockMode is 'optimistic', lockVersion is a Date, and the entity's updateDateColumn value does not equal the expected Date. The Date-based variant compares timestamps of an @UpdateDateColumn to detect concurrent modification.","triggerScenarios":"Two concurrent transactions read the same row, both call .setLock('optimistic', lastUpdateDate), and the second's getOne() finds the updateDate already advanced by the first commit. Also triggered by system-clock skew between write and read, or by manually setting the column.","commonSituations":"High-contention rows (counters, shared resources); a retry loop that re-reads but reuses a stale Date; tests that freeze time then advance it inconsistently; clock changes on the DB host.","solutions":["Re-read the entity to refresh the updateDate, then retry the operation (optimistic-lock retry loop).","Switch to a numeric @VersionColumn if you control the schema, for deterministic version bumps.","Ensure only one writer path mutates the row, or use pessimistic locking for hot rows."],"exampleFix":"// before\nconst qb = repo.createQueryBuilder().setLock('optimistic', staleUpdateDate);\nconst row = await qb.getOne(); // throws if row was touched\n// after\nlet row;\ntry { row = await repo.createQueryBuilder().setLock('optimistic', staleUpdateDate).getOne(); }\ncatch (e) { if (e.name === 'OptimisticLockVersionMismatchError') { row = await repo.findOneBy({ id }); /* retry */ } else throw e; }","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isOptimisticVersionMismatch(e: unknown): boolean {\n  return e instanceof Error && e.name === 'OptimisticLockVersionMismatchError';\n}","tryCatchPattern":"async function readOptimistic<T>(repo: Repository<T>, id: any, attempts = 3): Promise<T> {\n  for (let i = 0; i < attempts; i++) {\n    const current = await repo.findOneByOrFail({ id });\n    const updateDate = (current as any).updatedAt; // @UpdateDateColumn\n    try {\n      return await repo.createQueryBuilder()\n        .setLock('optimistic', new Date(updateDate))\n        .where({ id }).getOne() as Promise<T>;\n    } catch (e) {\n      if (!isOptimisticVersionMismatch(e) || i === attempts - 1) throw e;\n    }\n  }\n  throw new Error('unreachable');\n}","preventionTips":["Re-fetch the entity to refresh the updateDate before each retry.","Bound the retry count and surface a conflict error to the caller when exhausted.","Prefer numeric @VersionColumn for deterministic concurrency control where you control the schema.","Watch for clock skew between application and database hosts."],"tags":["typeorm","query-builder","optimistic-locking","concurrency","transactions"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}