n8n-io/n8n · error · TypeORMError

Circular eager relations are disallowed. ${entityMetadata.ta

Error message

Circular eager relations are disallowed. ${entityMetadata.targetName}#${relation.propertyPath} contains "eager: true", and its inverse side ${relation.inverseEntityMetadata.targetName}#${relation.inverseRelation.propertyPath} contains "eager: true" as well. Remove "eager: true" from one side of the relation.

What it means

TypeORM rejects relations where BOTH sides are marked `eager: true`. Eager loading pulls the related rows on every fetch of the parent; if each side is eager, loading A would load B which would eagerly load A again, recursing without end. The validator scans `entityMetadata.eagerRelations` and throws when an inverse relation is also eager.

Source

Thrown at packages/@n8n/typeorm/src/metadata-builder/EntityMetadataValidator.ts:279

				});
		});
		try {
			graph.overallOrder();
		} catch (err) {
			throw new CircularRelationsError(
				err.toString().replace('Error: Dependency Cycle Found: ', ''),
			);
		}
	}

	/**
	 * Validates eager relations to prevent circular dependency in them.
	 */
	protected validateEagerRelations(entityMetadatas: EntityMetadata[]) {
		entityMetadatas.forEach((entityMetadata) => {
			entityMetadata.eagerRelations.forEach((relation) => {
				if (relation.inverseRelation && relation.inverseRelation.isEager)
					throw new TypeORMError(
						`Circular eager relations are disallowed. ` +
							`${entityMetadata.targetName}#${relation.propertyPath} contains "eager: true", and its inverse side ` +
							`${relation.inverseEntityMetadata.targetName}#${relation.inverseRelation.propertyPath} contains "eager: true" as well.` +
							` Remove "eager: true" from one side of the relation.`,
					);
			});
		});
	}
}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Remove `eager: true` from one side; keep eager only on the side you most often traverse.
  2. Better: drop `eager` entirely and load relations explicitly via `relations: [...]` in find options or QueryBuilder `leftJoinAndSelect`.
  3. Use `relation.load()` (lazy relation) on the side you rarely need.

Example fix

// before
@OneToMany(() => Order, o => o.customer, { eager: true }) orders: Order[];
@ManyToOne(() => Customer, c => c.orders, { eager: true }) customer: Customer;

// after — eager on one side only
@OneToMany(() => Order, o => o.customer, { eager: true }) orders: Order[];
@ManyToOne(() => Customer, c => c.orders) customer: Customer;
Defensive patterns

Strategy: validation

Validate before calling

for (const m of dataSource.entityMetadatas) {
  for (const r of m.eagerRelations) {
    if (r.inverseRelation?.isEager) {
      throw new Error(`Mutual eager: ${m.name}#${r.propertyName} <-> ${r.inverseRelation.entityMetadata.name}`);
    }
  }
}

Try / catch

try { await dataSource.initialize(); } catch (e) { if (e instanceof TypeORMError && /Circular eager relations/) { /* drop eager:true from one side */ } throw e; }

Prevention

When it happens

Trigger: Two entities with a bidirectional @OneToMany/@ManyToOne or @ManyToMany where both decorators carry `eager: true`. A @OneToOne with `eager: true` on the owner and also on the inverse.

Common situations: Adding `eager: true` to make 'the relation always loads' on both ends for convenience. Copying an eager relation to its inverse during a refactor.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/f9622e0a5ef1e38a. Report an issue: GitHub.