{"record":{"id":"ddacbb96353521a0","repo":"koala73/worldmonitor","slug":"account-not-found","errorCode":null,"errorMessage":"ACCOUNT_NOT_FOUND","messagePattern":"ACCOUNT_NOT_FOUND","errorType":"exception","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/companyMonitoring/accounts.ts","lineNumber":427,"sourceCode":"  });\n  await scheduleScopedKeyCacheInvalidation(ctx, ownerUserId);\n  await scheduleAccountPurge(ctx, ownerFenceHash, purgeGeneration);\n  return ctx.db.get(account._id);\n}\n\nexport const markOwnerDeleted = internalMutation({\n  args: { ownerUserId: v.string() },\n  handler: async (ctx, args) => terminalize(ctx, args.ownerUserId, \"owner_deleted\"),\n});\n\nexport const markAccountDeleted = internalMutation({\n  args: { ownerAccountId: v.string() },\n  handler: async (ctx, args) => {\n    const account = await ctx.db\n      .query(\"companyMonitoringAccounts\")\n      .withIndex(\"by_logicalAccountId\", (q) => q.eq(\"logicalAccountId\", args.ownerAccountId))\n      .unique();\n    if (!account || !account.ownerUserId) throw new ConvexError(\"ACCOUNT_NOT_FOUND\");\n    return terminalize(ctx, account.ownerUserId, \"account_deleted\", account);\n  },\n});\n\nexport const advanceAccountPurge = internalMutation({\n  args: { ownerFenceHash: v.string(), purgeGeneration: v.number() },\n  handler: async (ctx, args) => {\n    const account = await ctx.db\n      .query(\"companyMonitoringAccounts\")\n      .withIndex(\"by_ownerFenceHash\", (q) => q.eq(\"ownerFenceHash\", args.ownerFenceHash))\n      .unique();\n    if (!account || account.purgeGeneration !== args.purgeGeneration) return { status: \"stale\" };\n    if (account.purgePhase === \"none\" || account.purgePhase === \"complete\") {\n      return { status: \"complete\" };\n    }\n\n    const now = Date.now();\n    if (account.purgePhase === \"pending\") {","sourceCodeStart":409,"sourceCodeEnd":445,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/companyMonitoring/accounts.ts#L409-L445","documentation":"Thrown by markAccountDeleted (accounts.ts:427) when no companyMonitoringAccounts row matches the provided ownerAccountId (matched via by_logicalAccountId index with .unique()), or when the matched row has no ownerUserId. This is the account-deletion entry point triggered with a logical account id, and it refuses to proceed if the target does not exist or is not owner-bound.","triggerScenarios":"Calling markAccountDeleted with an ownerAccountId (logical id like 'cm_account_...') that was never created, was already deleted, or has a typo. The row exists but its ownerUserId field is null/undefined (already terminalized — terminal rows strip ownerUserId).","commonSituations":"A deletion webhook fires for an account that was already terminalized by a prior deletion event. The ownerAccountId was transcribed incorrectly. The account row was manually deleted from the DB. A retry of a deletion event arrives after the account is already gone.","solutions":["Verify the ownerAccountId exists: query companyMonitoringAccounts by logicalAccountId via the dashboard or `npx convex run`.","If the row is already terminalized (ownerUserId stripped, terminalReason set), the deletion is already complete — treat as idempotent success and do not retry.","If the id is wrong, obtain the correct logicalAccountId from the account row for the intended owner.","Make the deletion-triggering caller idempotent: if ACCOUNT_NOT_FOUND, check whether the row is already terminal before surfacing an error."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before calling markAccountDeleted, verify the account exists and is not already terminal:\nconst account = await ctx.db\n  .query(\"companyMonitoringAccounts\")\n  .withIndex(\"by_logicalAccountId\", (q) => q.eq(\"logicalAccountId\", ownerAccountId))\n  .unique();\nif (!account) {\n  // already gone or never existed — treat as idempotent success\n  return { status: \"already-absent\" };\n}\nif (!account.ownerUserId) {\n  // already terminalized — nothing to do\n  return { status: \"already-terminal\" };\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ctx.runMutation(internal.companyMonitoring.accounts.markAccountDeleted, { ownerAccountId });\n} catch (err) {\n  if (err instanceof ConvexError && err.message === \"ACCOUNT_NOT_FOUND\") {\n    // idempotent: account already deleted or never existed — log and continue\n    return { status: \"not-found\" };\n  }\n  throw err;\n}","preventionTips":["Make deletion-triggering callers idempotent: treat ACCOUNT_NOT_FOUND as 'already deleted', not an error.","Verify the logicalAccountId exists before calling markAccountDeleted if you need to distinguish 'never existed' from 'already terminal'.","Pass the correct logicalAccountId (cm_account_...) from the caller, not a Convex _id or ownerUserId."],"tags":["convex","company-monitoring","not-found","terminalization","idempotency"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}