n8n-io/n8n · error · OptimisticLockCanNotBeUsedError
The optimistic lock can be used only with getOne() method.
Error message
The optimistic lock can be used only with getOne() method.
What it means
Thrown as OptimisticLockCanNotBeUsedError from getRawMany (and therefore getRawOne, which delegates to it) when expressionMap.lockMode === 'optimistic'. Optimistic locking requires materializing entity metadata to compare version columns, which the raw-result path does not do, so it refuses to run.
Source
Thrown at packages/@n8n/typeorm/src/query-builder/SelectQueryBuilder.ts:1475
* Disables the global condition of "non-deleted" for the entity with delete date columns.
*/
withDeleted(): this {
this.expressionMap.withDeleted = true;
return this;
}
/**
* Gets first raw result returned by execution of generated query builder sql.
*/
async getRawOne<T = any>(): Promise<T | undefined> {
return (await this.getRawMany())[0];
}
/**
* Gets all raw results returned by execution of generated query builder sql.
*/
async getRawMany<T = any>(): Promise<T[]> {
if (this.expressionMap.lockMode === 'optimistic') throw new OptimisticLockCanNotBeUsedError();
this.expressionMap.queryEntity = false;
const queryRunner = this.obtainQueryRunner();
let transactionStartedByUs: boolean = false;
try {
// start transaction if it was enabled
if (this.expressionMap.useTransaction === true && queryRunner.isTransactionActive === false) {
await queryRunner.startTransaction();
transactionStartedByUs = true;
}
const results = await this.loadRawResults(queryRunner);
// close transaction if we started it
if (transactionStartedByUs) {
await queryRunner.commitTransaction();
}
View on GitHub (pinned to 5ac6606e81)
Solutions
- Use getOne() (or getMany()) instead of getRawMany()/getRawOne() when optimistic locking is set.
- If you need raw results, remove the optimistic lock for that query path: split into two query builders.
- Assert lockMode before choosing the read method so the mismatch is caught in tests.
Example fix
// before
qb.setLock('optimistic', version).getRawOne();
// after
qb.setLock('optimistic', version).getOne(); Defensive patterns
Strategy: validation
Validate before calling
function assertSafeForRaw(qb: SelectQueryBuilder<any>): void {
// Optimistic locks cannot be used with raw reads
if ((qb as any).expressionMap?.lockMode === 'optimistic') {
throw new Error('Optimistic lock is incompatible with getRawMany/getRawOne; use getOne/getMany or drop the lock.');
}
} Type guard
function isOptimisticLockQuery(qb: { expressionMap?: { lockMode?: string } }): boolean {
return qb.expressionMap?.lockMode === 'optimistic';
} Try / catch
try {
return await qb.getRawMany();
} catch (e) {
if (e?.name === 'OptimisticLockCanNotBeUsedError' || /optimistic lock/i.test(e?.message ?? '')) {
return await qb.getMany(); // fall back to entity read
}
throw e;
} Prevention
- Decide per query path whether you need entities (getOne/getMany) or raw rows (getRawOne/getRawMany).
- Never set an optimistic lock on a query builder reused for raw reads.
- Add a unit test that asserts raw-read methods throw when an optimistic lock is configured.
When it happens
Trigger: Calling `.setLock('optimistic', version).getRawMany()` or `.getRawOne()`. Mixing `.setLock('optimistic', ...)` with raw projections.
Common situations: Refactoring a query from getOne() to getRawMany() for performance while leaving an optimistic-lock set; copy-paste of a query-builder chain across read paths; enabling optimistic locking globally in a base repository method that is also used for raw reads.
Related errors
- The optimistic lock on entity ${entity} failed, version ${ex
- Property "${propertyPath}" was not found in "${metadata.targ
- Cannot query across ${relation.relationType} for property ${
- "${aliasName}" alias was not found. Maybe you forgot to join
- Entity to work with is not specified!
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/7aec4d213db905fd.
Report an issue: GitHub.