koala73/worldmonitor · error · ConvexError
COMPANY_MONITORING_ACCOUNT_INACTIVE
Error message
COMPANY_MONITORING_ACCOUNT_INACTIVE
What it means
Thrown by scheduleAccountWorkHandler when the owner account is missing, its lifecycle is not 'entitled', or it has a terminalReason. Scan scheduling is only permitted for fully-entitled, non-terminal accounts, so this guard stops new work from being queued for accounts that can no longer consume it.
Source
Thrown at convex/companyMonitoring/orchestration.ts:285
await ctx.db.patch(account._id, {
nextExaScanDueAt,
nextXScanDueAt,
updatedAt: Date.now(),
});
}
async function scheduleAccountWorkHandler(
ctx: MutationCtx,
args: { ownerAccountId: string; source: Source; companyIds: string[] },
mode: "strict" | "missing_only" = "strict",
) {
const now = Date.now();
const account = await ctx.db
.query("companyMonitoringAccounts")
.withIndex("by_logicalAccountId", (q) => q.eq("logicalAccountId", args.ownerAccountId))
.unique();
if (!account || account.lifecycle !== "entitled" || account.terminalReason) {
throw new ConvexError("COMPANY_MONITORING_ACCOUNT_INACTIVE");
}
requireProviderClaimPolicy(account, args.source);
const requestedCompanyIds = [...new Set(args.companyIds)].sort();
if (
requestedCompanyIds.length === 0 ||
requestedCompanyIds.length > COMPANY_MONITORING_SCAN_COHORT_LIMIT
) {
throw new ConvexError("INVALID_COMPANY_MONITORING_COHORT");
}
const rows = await Promise.all(requestedCompanyIds.map(async (companyId) => {
const [company, obligation] = await Promise.all([
ctx.db
.query("companyMonitoringCompanies")
.withIndex("by_account_companyId", (q) =>
q.eq("ownerAccountId", args.ownerAccountId).eq("companyId", companyId),
)View on GitHub (pinned to ffec79ac33)
Solutions
- Before scheduling, confirm the account is lifecycle 'entitled' with no terminalReason.
- Ensure account teardown cancels queued scheduler callbacks (runAfter) for that account.
- Treat this error as terminal for the scheduling path: do not retry blindly.
Example fix
// before
await queueAccountSourceWork(ctx, ownerAccountId, source, companyIds);
// after
const account = await fetchAccount(ownerAccountId);
if (!account || account.lifecycle !== "entitled" || account.terminalReason) {
return { status: "inactive" };
}
await queueAccountSourceWork(ctx, ownerAccountId, source, companyIds); Defensive patterns
Strategy: validation
Validate before calling
const account = await fetchAccount(ownerAccountId);
if (!account || account.lifecycle !== "entitled" || account.terminalReason) {
return { status: "inactive" };
} Type guard
function isSchedulableAccount(a: { lifecycle: string; terminalReason?: string } | null): boolean {
return Boolean(a) && a.lifecycle === "entitled" && !a.terminalReason;
} Prevention
- Confirm entitlement before queueing account work.
- Drain queued callbacks for an account during teardown.
When it happens
Trigger: Calling ensureAccountWork/scheduleAccountWork for an ownerAccountId whose account row is gone, suspended/cancelled (lifecycle != 'entitled'), or marked terminal.
Common situations: A queued follow-up mutation firing after the account was suspended; account teardown did not drain pending scheduler callbacks; billing lapse marking the account terminal.
Related errors
- COMPANY_MONITORING_ACCOUNT_INACTIVE
- COMPANY_MONITORING_ACCESS_DENIED
- COMPANY_MONITORING_ACCESS_DENIED
- COMPANY_MONITORING_ACCESS_DENIED
- INVALID_COMPANY_MONITORING_COHORT
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/e3c401559cbd3bbb.
Report an issue: GitHub.