langfuse/langfuse · error · LangfuseConflictError

buildTemplateInUseMessage(${names})

Error message

buildTemplateInUseMessage(${names})

What it means

Thrown as a LangfuseConflictError (409) when deleting an evaluator template that is still referenced by one or more evaluation rules. The message is built from the referencing rule names so users can see which rules block deletion. The service locks the evaluator row first, so the usage check is transactional.

Source

Thrown at web/src/features/evals/server/legacyCompatibilityService.ts:1487

      const version = await tx.evaluatorVersion.findFirst({
        where: { id: templateId, evaluator: { projectId } },
        select: { evaluatorId: true },
      });
      if (!version) throw new LangfuseNotFoundError("Evaluator not found");

      // Lock the evaluator so a rule cannot be assigned to it between the
      // usage check and the delete.
      await tx.$executeRaw`SELECT "id" FROM "evaluators" WHERE "id" = ${version.evaluatorId} AND "project_id" = ${projectId} FOR UPDATE`;

      const referencingRules = await tx.evaluationRule.findMany({
        where: {
          projectId,
          assignments: { some: { evaluatorId: version.evaluatorId } },
        },
        select: { name: true },
      });
      if (referencingRules.length > 0) {
        throw new LangfuseConflictError(
          buildTemplateInUseMessage(referencingRules.map(({ name }) => name)),
        );
      }

      const versions = await tx.evaluatorVersion.findMany({
        where: { evaluatorId: version.evaluatorId },
      });
      await tx.evaluator.delete({
        where: { id: version.evaluatorId, projectId },
      });
      return versions;
    });
  }
}

View on GitHub (pinned to 59d92c7cf3)

Solutions

  1. Remove or re-point the evaluation rules named in the error message first, then retry deletion
  2. Use the rule update API to detach the evaluator assignment from each referencing rule
  3. Parse the rule names from the 409 message to drive a UI prompt offering to detach them
  4. If rules are unused, deactivate and delete them before deleting the template
Defensive patterns

Strategy: try-catch

Validate before calling

const referencing = await listRulesUsingTemplate(projectId, templateId);
if (referencing.length === 0) {
  await svc.deleteTemplate(projectId, templateId);
}

Type guard

function isConflictError(e: unknown): e is { name: 'LangfuseConflictError'; message: string } {
  return e instanceof Error && (e as any).name === 'LangfuseConflictError';
}

Try / catch

try {
  await svc.deleteTemplate(projectId, templateId);
} catch (e) {
  if (isConflictError(e)) {
    // parse rule names from message, offer detach flow
    const rules = parseRuleNames(e.message);
    await detachEvaluatorFromRules(projectId, rules);
    await svc.deleteTemplate(projectId, templateId);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling deleteTemplate on a template while evaluation rules have assignments pointing to that template's evaluator; the referencingRules query inside the transaction returns one or more rule names.

Common situations: Trying to clean up old evaluator templates that are still attached to active or paused evaluation rules; bulk-deleting templates without first detaching them from rules.

Related errors


AI-assisted analysis of langfuse/langfuse@59d92c7cf3 (2026-08-27). Data as JSON: /api/errors/5956580624e81398. Report an issue: GitHub.