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
- Split the edit into chunks of at most maxClaimsPerCompany entries per mutation call
- Read COMPANY_MONITORING_LIMITS from the shared contract package and enforce the same cap in the UI (disable submit beyond it)
- For large imports, use the dedicated import path (clientImportId metadata) instead of patch
- 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
- Enforce maxClaimsPerCompany in the UI (cap multi-select and import previews)
- Import via the dedicated import path (clientImportId) for large claim sets, not patch
- Import COMPANY_MONITORING_LIMITS from the shared contract so client and server caps match
- Show the claim budget remaining before the user assembles the patch
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
- CLAIM_NOT_FOUND
- COMPANY_MONITORING_ADMISSION_QUERY_VERSION_INVALID
- COMPANY_MONITORING_EVIDENCE_REVISION_INVALID
- COMPANY_MONITORING_ _INVALID
- COMPANY_MONITORING_MODEL_VERSION_INVALID
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)