koala73/worldmonitor · error · ConvexError

ACCOUNT_NOT_FOUND

Error message

ACCOUNT_NOT_FOUND

What it means

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.

Source

Thrown at convex/companyMonitoring/accounts.ts:427

  });
  await scheduleScopedKeyCacheInvalidation(ctx, ownerUserId);
  await scheduleAccountPurge(ctx, ownerFenceHash, purgeGeneration);
  return ctx.db.get(account._id);
}

export const markOwnerDeleted = internalMutation({
  args: { ownerUserId: v.string() },
  handler: async (ctx, args) => terminalize(ctx, args.ownerUserId, "owner_deleted"),
});

export const markAccountDeleted = internalMutation({
  args: { ownerAccountId: v.string() },
  handler: async (ctx, args) => {
    const account = await ctx.db
      .query("companyMonitoringAccounts")
      .withIndex("by_logicalAccountId", (q) => q.eq("logicalAccountId", args.ownerAccountId))
      .unique();
    if (!account || !account.ownerUserId) throw new ConvexError("ACCOUNT_NOT_FOUND");
    return terminalize(ctx, account.ownerUserId, "account_deleted", account);
  },
});

export const advanceAccountPurge = internalMutation({
  args: { ownerFenceHash: v.string(), purgeGeneration: v.number() },
  handler: async (ctx, args) => {
    const account = await ctx.db
      .query("companyMonitoringAccounts")
      .withIndex("by_ownerFenceHash", (q) => q.eq("ownerFenceHash", args.ownerFenceHash))
      .unique();
    if (!account || account.purgeGeneration !== args.purgeGeneration) return { status: "stale" };
    if (account.purgePhase === "none" || account.purgePhase === "complete") {
      return { status: "complete" };
    }

    const now = Date.now();
    if (account.purgePhase === "pending") {

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Verify the ownerAccountId exists: query companyMonitoringAccounts by logicalAccountId via the dashboard or `npx convex run`.
  2. If the row is already terminalized (ownerUserId stripped, terminalReason set), the deletion is already complete — treat as idempotent success and do not retry.
  3. If the id is wrong, obtain the correct logicalAccountId from the account row for the intended owner.
  4. Make the deletion-triggering caller idempotent: if ACCOUNT_NOT_FOUND, check whether the row is already terminal before surfacing an error.
Defensive patterns

Strategy: validation

Validate before calling

// Before calling markAccountDeleted, verify the account exists and is not already terminal:
const account = await ctx.db
  .query("companyMonitoringAccounts")
  .withIndex("by_logicalAccountId", (q) => q.eq("logicalAccountId", ownerAccountId))
  .unique();
if (!account) {
  // already gone or never existed — treat as idempotent success
  return { status: "already-absent" };
}
if (!account.ownerUserId) {
  // already terminalized — nothing to do
  return { status: "already-terminal" };
}

Try / catch

try {
  await ctx.runMutation(internal.companyMonitoring.accounts.markAccountDeleted, { ownerAccountId });
} catch (err) {
  if (err instanceof ConvexError && err.message === "ACCOUNT_NOT_FOUND") {
    // idempotent: account already deleted or never existed — log and continue
    return { status: "not-found" };
  }
  throw err;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


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