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
- Clear or change the customerReference on the other company first (or remove that company), then retry the patch
- Validate uniqueness client-side before submitting: search the account's companies for the reference
- Make import tooling detect duplicate references in the source file before any mutation runs
- 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
- Validate reference uniqueness client-side against the current company list before submit
- Deduplicate reference columns in import files before running mutations
- Remember paused companies still hold their reference; only removal frees it
- When relabeling during a merge, clear the reference on the old company first
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)