{"record":{"id":"96d90b6330884cb4","repo":"koala73/worldmonitor","slug":"company-limit-reached","errorCode":"COMPANY_LIMIT_REACHED","errorMessage":"COMPANY_LIMIT_REACHED","messagePattern":"COMPANY_LIMIT_REACHED","errorType":"exception","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/companyMonitoring/companies.ts","lineNumber":137,"sourceCode":"      )\n      .unique();\n    if (replay) {\n      if (replay.directFingerprint !== directFingerprint) {\n        throw new ConvexError(\"REPLAY_CONFLICT\");\n      }\n      return { status: \"replayed\", companyId: replay.companyId };\n    }\n\n    const noop = await findNoopByCustomerReference(\n      ctx,\n      account.logicalAccountId,\n      company.customerReference,\n    );\n    if (noop) return { status: \"noop\", companyId: noop.companyId };\n\n    const companyCount = account.companyCount ?? 0;\n    if (companyCount >= (account.companyLimit ?? COMPANY_LIMIT)) {\n      throw new ConvexError(\"COMPANY_LIMIT_REACHED\");\n    }\n\n    const companyId = await insertNormalizedCompany(ctx, account, company, {\n      directRequestId: clientRequestId,\n      directFingerprint,\n    });\n    await scheduleCompanySourcesHandler(ctx, {\n      ownerAccountId: account.logicalAccountId,\n      companyId,\n    });\n    await ctx.db.patch(account._id, {\n      companyCount: companyCount + 1,\n      snapshotGeneration: (account.snapshotGeneration ?? 0) + 1,\n      updatedAt: Date.now(),\n    });\n    return { status: \"created\", companyId };\n  },\n});","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/convex/companyMonitoring/companies.ts#L119-L155","documentation":"Thrown by createCompanyForOwner (convex/companyMonitoring/companies.ts:137) when the account's companyCount has reached its limit — account.companyLimit if set, otherwise the default COMPANY_LIMIT. Adding another monitored company is refused before any insert happens.","triggerScenarios":"Calling add-company after the account already tracks the maximum number of companies; companyLimit was lowered (plan change) below the current companyCount, so even the next add fails; concurrent adds racing the companyCount check when the limit is nearly reached.","commonSituations":"Plan downgrade shrinking limits; bulk imports hitting the ceiling mid-batch; default limit surprise in staging with COMPANY_LIMIT lower than production expectations; count drift after failed deletes.","solutions":["Surface an upgrade-path message to the user (raise account.companyLimit) or have them remove companies to free slots","Check the count before submitting: query the account's companyCount/companyLimit and block the UI action early","For imports, pre-compute how many slots remain and stop or chunk the batch accordingly","If count looks wrong, verify that removals actually decrement companyCount (the patch does companyCount + 1 on insert, so the inverse must run on removal)"],"exampleFix":"// before\nawait api.companyMonitoring.createCompanyForOwner({ ownerUserId, clientRequestId, company });\n\n// after\nconst account = await api.companyMonitoring.getAccount({ ownerUserId });\nconst limit = account.companyLimit ?? DEFAULT_COMPANY_LIMIT;\nif ((account.companyCount ?? 0) >= limit) {\n  throw new UserFacingError(`Company limit reached (${limit}). Remove a company or upgrade.`);\n}\nawait api.companyMonitoring.createCompanyForOwner({ ownerUserId, clientRequestId, company });","handlingStrategy":"validation","validationCode":"const account = await api.companyMonitoring.getAccount({ ownerUserId });\nconst limit = account.companyLimit ?? DEFAULT_COMPANY_LIMIT;\nconst count = account.companyCount ?? 0;\nif (count >= limit) {\n  throw new UserFacingError(`Company limit reached (${count}/${limit}). Remove a company or upgrade the plan.`);\n}\nawait api.companyMonitoring.createCompanyForOwner({ ownerUserId, clientRequestId, company });","typeGuard":null,"tryCatchPattern":"try {\n  await api.companyMonitoring.createCompanyForOwner({ ownerUserId, clientRequestId, company });\n} catch (err) {\n  if (err instanceof ConvexError && err.data === \"COMPANY_LIMIT_REACHED\") {\n    ui.showUpgradePrompt(); // or guide the user to remove companies\n    return;\n  }\n  throw err;\n}","preventionTips":["Show remaining slots (companyCount/companyLimit) in the UI before the add action","Chunk imports to the remaining slots and report the shortfall instead of failing mid-batch","After plan changes, reconcile companyLimit with companyCount before the next add","Verify removals actually decrement companyCount so slots are freed"],"tags":["convex","quota","limit","plan","mutation"],"backgroundTag":"quota-limit-exceeded","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"}