koala73/worldmonitor · error · ConvexError

CUSTOMER_REFERENCE_CONFLICT

CUSTOMER_REFERENCE_CONFLICT

Error message

CUSTOMER_REFERENCE_CONFLICT

What it means

Thrown by the company update mutation (convex/companyMonitoring/companies.ts:231) when the patch sets a customerReference that is already used by a different active or paused company on the same account (findNoopByCustomerReference). Customer references must be unique per account across non-removed companies.

Solutions

  1. Clear or change the customerReference on the other company first (or remove that company), then retry the patch
  2. Validate uniqueness client-side before submitting: search the account's companies for the reference
  3. Make import tooling detect duplicate references in the source file before any mutation runs
  4. Remember lifecycle: active and paused both conflict; only removed releases the reference

Example fix

// before
await api.companyMonitoring.updateCompany({
  ownerUserId, companyId,
  patch: { customerReference: "ACME-001" }, // already on another company
});

// after
const existing = await api.companyMonitoring.listCompanies({ ownerUserId });
const clash = existing.find((c) => c.customerReference === "ACME-001" && c.companyId !== companyId);
if (clash) throw new UserFacingError(`Reference already used by ${clash.name}`);
await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch: { customerReference: "ACME-001" } });
Defensive patterns

Strategy: validation

Validate before calling

const companies = await api.companyMonitoring.listCompanies({ ownerUserId });
const clash = companies.find(
  (c) => c.customerReference === nextReference && c.companyId !== companyId && (c.lifecycle === "active" || c.lifecycle === "paused"),
);
if (clash) {
  throw new UserFacingError(`Reference already used by "${clash.name}" (${clash.lifecycle}).`);
}
await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch: { customerReference: nextReference } });

Type guard

function isCustomerReferenceFree(companies: { companyId: string; customerReference?: string; lifecycle: string }[], nextReference: string, companyId: string): boolean {
  return !companies.some(
    (c) => c.customerReference === nextReference && c.companyId !== companyId && (c.lifecycle === "active" || c.lifecycle === "paused"),
  );
}

Try / catch

try {
  await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch: { customerReference: nextReference } });
} catch (err) {
  if (err instanceof ConvexError && err.data === "CUSTOMER_REFERENCE_CONFLICT") {
    ui.fieldError("customerReference", "Already used by another active or paused company.");
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Editing company A's customerReference to the value already carried by company B; merging duplicates by relabeling instead of removing; pasting a reference from another company's record; re-importing a reference that still belongs to a paused company.

Common situations: ERP/CRM syncs where external refs get reassigned but the old company keeps its copy; import files with duplicated reference columns; paused companies still holding the reference (pause does not free it — only removal does).

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21). Data as JSON: /api/errors/73029eb5c633e628. Report an issue: GitHub.

Appendix: source

Thrown at convex/companyMonitoring/companies.ts:231

    const hasName = Object.prototype.hasOwnProperty.call(patch, "name");
    const hasDomicile = Object.prototype.hasOwnProperty.call(patch, "domicileCountry");
    const hasCustomerReference = Object.prototype.hasOwnProperty.call(patch, "customerReference");
    const normalizedFields = normalizeMonitoredCompanyInput({
      name: hasName ? patch.name! : company.name,
      domicileCountry: hasDomicile ? patch.domicileCountry! : company.domicileCountry,
      customerReference: hasCustomerReference
        ? patch.customerReference
        : company.customerReference,
    });
    if (hasCustomerReference && normalizedFields.customerReference) {
      const conflict = await findNoopByCustomerReference(
        ctx,
        account.logicalAccountId,
        normalizedFields.customerReference,
      );
      if (conflict && conflict.companyId !== company.companyId) {
        throw new ConvexError("CUSTOMER_REFERENCE_CONFLICT");
      }
    }

    const currentClaims = await ctx.db
      .query("companyMonitoringClaims")
      .withIndex("by_account_company", (q) =>
        q.eq("ownerAccountId", account.logicalAccountId).eq("companyId", company.companyId),
      )
      .collect();
    const currentById = new Map(currentClaims.map((claim) => [claim.claimId, claim]));
    const removeIds = new Set(
      removeClaimInputs.map((claimId) => assertLogicalId("claim", String(claimId))),
    );
    for (const claimId of removeIds) {
      if (!currentById.has(claimId)) throw new ConvexError("CLAIM_NOT_FOUND");
    }
    if (hasCustomerReference && normalizedFields.customerReference !== company.customerReference) {
      for (const claim of currentClaims) {

View on GitHub (pinned to 7d06c8633d)