koala73/worldmonitor · error · ConvexError

CLAIM_NOT_FOUND

Error message

CLAIM_NOT_FOUND

What it means

Thrown by updateCompanyForOwner when a claim id listed in removeClaimIds does not correspond to any existing claim on the target company. Each removal id is validated against the by_account_company claims map (currentById) built from the live claim rows, so the id must be a real claimId owned by that company.

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 ffec79ac33)

Solutions

  1. Refresh the company's current claims before issuing the removal patch.
  2. Tolerate already-removed claims client-side by filtering the removeClaimIds to only those still present.
  3. If the removal is best-effort, split removals so a missing one does not abort the whole patch.

Example fix

// before
await update({ companyId, patch: { removeClaimIds: cachedIds } });

// after
const claims = await listClaims(companyId);
const stillPresent = cachedIds.filter((id) => claims.some((c) => c.claimId === id));
await update({ companyId, patch: { removeClaimIds: stillPresent } });
Defensive patterns

Strategy: validation

Validate before calling

const current = await listClaimsForCompany(ownerUserId, companyId);
const knownIds = new Set(current.map((c) => c.claimId));
const validRemovals = removeClaimIds.filter((id) => knownIds.has(id));
await updateCompanyForOwner({ ownerUserId, companyId, patch: { removeClaimIds: validRemovals } });

Type guard

function everyRemovalExists(current: { claimId: string }[], removals: string[]): boolean {
  const known = new Set(current.map((c) => c.claimId));
  return removals.every((id) => known.has(id));
}

Prevention

When it happens

Trigger: Passing a removeClaimIds entry that is not a current claimId of the patched company: a stale id, an id from a different company, an id already removed in a concurrent update, or a malformed logical id.

Common situations: UI caching a claim list that has since changed; concurrent edits where one removes a claim another is also removing; copy/paste of a claim id across companies.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/5654f7939986bb82. Report an issue: GitHub.