{"record":{"id":"5654f7939986bb82","repo":"koala73/worldmonitor","slug":"claim-not-found","errorCode":"CLAIM_NOT_FOUND","errorMessage":"CLAIM_NOT_FOUND","messagePattern":"CLAIM_NOT_FOUND","errorType":"exception","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/companyMonitoring/companies.ts","lineNumber":246,"sourceCode":"        normalizedFields.customerReference,\n      );\n      if (conflict && conflict.companyId !== company.companyId) {\n        throw new ConvexError(\"CUSTOMER_REFERENCE_CONFLICT\");\n      }\n    }\n\n    const currentClaims = await ctx.db\n      .query(\"companyMonitoringClaims\")\n      .withIndex(\"by_account_company\", (q) =>\n        q.eq(\"ownerAccountId\", account.logicalAccountId).eq(\"companyId\", company.companyId),\n      )\n      .collect();\n    const currentById = new Map(currentClaims.map((claim) => [claim.claimId, claim]));\n    const removeIds = new Set(\n      removeClaimInputs.map((claimId) => assertLogicalId(\"claim\", String(claimId))),\n    );\n    for (const claimId of removeIds) {\n      if (!currentById.has(claimId)) throw new ConvexError(\"CLAIM_NOT_FOUND\");\n    }\n    if (hasCustomerReference && normalizedFields.customerReference !== company.customerReference) {\n      for (const claim of currentClaims) {\n        if (claim.type === \"customer_reference\") removeIds.add(claim.claimId);\n      }\n    }\n\n    const remainingKeys = new Set(\n      currentClaims\n        .filter((claim) => !removeIds.has(claim.claimId))\n        .map((claim) => `${claim.type}\\u0000${claim.value}`),\n    );\n    const normalizedAdditions = addClaimInputs.map(normalizeCompanyClaimInput);\n    if (normalizedFields.name !== company.name) {\n      normalizedAdditions.unshift({ type: \"alias\", value: normalizedFields.name });\n    }\n    if (hasCustomerReference && normalizedFields.customerReference) {\n      normalizedAdditions.push({","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/convex/companyMonitoring/companies.ts#L228-L264","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait api.companyMonitoring.updateCompany({\n  ownerUserId, companyId,\n  patch: { removeClaimIds: selectedClaimIds }, // may include already-deleted ids\n});\n\n// after\nconst current = await api.companyMonitoring.listClaims({ ownerUserId, companyId });\nconst currentIds = new Set(current.map((c) => c.claimId));\nconst removeClaimIds = selectedClaimIds.filter((id) => currentIds.has(id)); // missing ids become no-ops\nif (removeClaimIds.length > 0) {\n  await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch: { removeClaimIds } });\n}","handlingStrategy":"validation","validationCode":"const current = await api.companyMonitoring.listClaims({ ownerUserId, companyId });\nconst currentIds = new Set(current.map((c) => c.claimId));\nconst removeClaimIds = requestedRemoveIds.filter((id) => currentIds.has(id)); // missing ids become no-ops\nif (removeClaimIds.length > 0) {\n  await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch: { removeClaimIds } });\n}","typeGuard":"function existingClaimIds(current: { claimId: string }[], requested: string[]): Set<string> {\n  const known = new Set(current.map((c) => c.claimId));\n  return new Set(requested.filter((id) => known.has(id)));\n}","tryCatchPattern":"try {\n  await api.companyMonitoring.updateCompany({ ownerUserId, companyId, patch });\n} catch (err) {\n  if (err instanceof ConvexError && err.data === \"CLAIM_NOT_FOUND\") {\n    const fresh = await api.companyMonitoring.listClaims({ ownerUserId, companyId });\n    const known = new Set(fresh.map((c) => c.claimId));\n    await api.companyMonitoring.updateCompany({\n      ownerUserId, companyId,\n      patch: { ...patch, removeClaimIds: (patch.removeClaimIds ?? []).filter((id) => known.has(id)) },\n    });\n    return;\n  }\n  throw err;\n}","preventionTips":["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"],"tags":["convex","claims","not-found","stale-state","mutation"],"backgroundTag":"referenced-record-not-found","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}