koala73/worldmonitor · error · ConvexError
CLAIM_NOT_FOUND
CLAIM_NOT_FOUND
Error message
CLAIM_NOT_FOUND
What it means
Thrown by the company update mutation (convex/companyMonitoring/companies.ts:246) when patch.removeClaimIds contains a claimId that does not exist among the company's current claims (queried via by_account_company). Every id in removeClaimIds must resolve to an existing claim, and ids are also run through assertLogicalId first.
Solutions
- Re-fetch the company's current claims and filter removeClaimIds to existing ids before submitting
- If your UX treats removing-an-already-removed claim as a no-op, compute the intersection and drop missing ids client-side
- Never send add and remove for the same claim value in one patch; separate the calls
- Reload claim state after any failed patch instead of resubmitting verbatim
Example fix
// before
await api.companyMonitoring.updateCompany({
ownerUserId, companyId,
patch: { removeClaimIds: selectedClaimIds }, // may include already-deleted ids
});
// after
const current = await api.companyMonitoring.listClaims({ ownerUserId, companyId });
const currentIds = new Set(current.map((c) => c.claimId));
const removeClaimIds = selectedClaimIds.filter((id) => currentIds.has(id)); // missing ids become no-ops
if (removeClaimIds.length > 0) {
await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch: { removeClaimIds } });
} Defensive patterns
Strategy: validation
Validate before calling
const current = await api.companyMonitoring.listClaims({ ownerUserId, companyId });
const currentIds = new Set(current.map((c) => c.claimId));
const removeClaimIds = requestedRemoveIds.filter((id) => currentIds.has(id)); // missing ids become no-ops
if (removeClaimIds.length > 0) {
await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch: { removeClaimIds } });
} Type guard
function existingClaimIds(current: { claimId: string }[], requested: string[]): Set<string> {
const known = new Set(current.map((c) => c.claimId));
return new Set(requested.filter((id) => known.has(id)));
} Try / catch
try {
await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch });
} catch (err) {
if (err instanceof ConvexError && err.data === "CLAIM_NOT_FOUND") {
const fresh = await api.companyMonitoring.listClaims({ ownerUserId, companyId });
const known = new Set(fresh.map((c) => c.claimId));
await api.companyMonitoring.updateCompany({
ownerUserId, companyId,
patch: { ...patch, removeClaimIds: (patch.removeClaimIds ?? []).filter((id) => known.has(id)) },
});
return;
}
throw err;
} Prevention
- Re-fetch claims before building the remove list; never trust a cached selection
- Treat already-removed claims as success in UX instead of resubmitting the raw list
- Never mix add and remove for the same claim in a single patch
- Disable claim controls while a mutation is in flight to avoid concurrent-edit staleness
When it happens
Trigger: Removing a claim that was already deleted in another session; stale UI state listing claims removed moments earlier; sending addClaims and removeClaimIds for the same id in one patch (the add has not landed when removal is validated); claim ids from a different company.
Common situations: Two editors editing claims concurrently; client cache behind the server; retry of an old patch after claims changed; scripts replaying recorded patches against refreshed data.
Related errors
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21).
Data as JSON: /api/errors/5654f7939986bb82.
Report an issue: GitHub.
Appendix: source
Thrown at convex/companyMonitoring/companies.ts:246
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) {
if (claim.type === "customer_reference") removeIds.add(claim.claimId);
}
}
const remainingKeys = new Set(
currentClaims
.filter((claim) => !removeIds.has(claim.claimId))
.map((claim) => `${claim.type}\u0000${claim.value}`),
);
const normalizedAdditions = addClaimInputs.map(normalizeCompanyClaimInput);
if (normalizedFields.name !== company.name) {
normalizedAdditions.unshift({ type: "alias", value: normalizedFields.name });
}
if (hasCustomerReference && normalizedFields.customerReference) {
normalizedAdditions.push({View on GitHub (pinned to 7d06c8633d)