koala73/worldmonitor · error · ConvexError

INVALID_COMPANY_PATCH

INVALID_COMPANY_PATCH

Error message

INVALID_COMPANY_PATCH

What it means

Thrown by the company update mutation (convex/companyMonitoring/companies.ts:211) when patch.addClaims or patch.removeClaimIds exceeds COMPANY_MONITORING_LIMITS.maxClaimsPerCompany. Each update request may add or remove at most that many claims in one call.

Solutions

  1. Split the edit into chunks of at most maxClaimsPerCompany entries per mutation call
  2. Read COMPANY_MONITORING_LIMITS from the shared contract package and enforce the same cap in the UI (disable submit beyond it)
  3. For large imports, use the dedicated import path (clientImportId metadata) instead of patch
  4. Show the remaining claim budget to the user before they assemble the patch

Example fix

// before
await api.companyMonitoring.updateCompany({
  ownerUserId, companyId,
  patch: { addClaims: allImportedClaims }, // 200+ claims -> INVALID_COMPANY_PATCH
});

// after
const CHUNK = COMPANY_MONITORING_LIMITS.maxClaimsPerCompany;
for (let i = 0; i < allImportedClaims.length; i += CHUNK) {
  await api.companyMonitoring.updateCompany({
    ownerUserId, companyId,
    patch: { addClaims: allImportedClaims.slice(i, i + CHUNK) },
  });
}
Defensive patterns

Strategy: validation

Validate before calling

const CHUNK = COMPANY_MONITORING_LIMITS.maxClaimsPerCompany; // from the shared contract
function chunkClaims<T>(items: T[]): T[][] {
  const out: T[][] = [];
  for (let i = 0; i < items.length; i += CHUNK) out.push(items.slice(i, i + CHUNK));
  return out;
}
if (patch.addClaims.length > CHUNK || (patch.removeClaimIds ?? []).length > CHUNK) {
  for (const addChunk of chunkClaims(patch.addClaims)) {
    await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch: { addClaims: addChunk } });
  }
} else {
  await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch });
}

Try / catch

try {
  await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch });
} catch (err) {
  if (err instanceof ConvexError && err.data === "INVALID_COMPANY_PATCH") {
    const CHUNK = COMPANY_MONITORING_LIMITS.maxClaimsPerCompany;
    for (let i = 0; i < patch.addClaims.length; i += CHUNK) {
      await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch: { addClaims: patch.addClaims.slice(i, i + CHUNK) } });
    }
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Submitting a bulk claim edit (e.g. import 200 claims at once) in a single patch; a select-all-then-remove UI action generating a huge removeClaimIds array; concatenating multiple edits into one patch to save round trips.

Common situations: Spreadsheet/CSV importers mapping every row to addClaims; UIs with no client-side cap on multi-select; limit value lowered in a deploy while older clients still send big batches.

Related errors


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

Appendix: source

Thrown at convex/companyMonitoring/companies.ts:211

  handler: async (ctx, args) => {
    const account = await requireActiveAccount(ctx, args.ownerUserId);
    const company = await ctx.db
      .query("companyMonitoringCompanies")
      .withIndex("by_account_companyId", (q) =>
        q.eq("ownerAccountId", account.logicalAccountId).eq("companyId", args.companyId),
      )
      .unique();
    if (!company || company.lifecycle === "removed" || !company.name || !company.domicileCountry) {
      throw new ConvexError("NOT_FOUND");
    }
    const patch = args.patch;
    const addClaimInputs = patch.addClaims ?? [];
    const removeClaimInputs = patch.removeClaimIds ?? [];
    if (
      addClaimInputs.length > COMPANY_MONITORING_LIMITS.maxClaimsPerCompany ||
      removeClaimInputs.length > COMPANY_MONITORING_LIMITS.maxClaimsPerCompany
    ) {
      throw new ConvexError("INVALID_COMPANY_PATCH");
    }

    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,
      );

View on GitHub (pinned to 7d06c8633d)