koala73/worldmonitor · error · ConvexError
INVALID_COMPANY_MONITORING_COHORT
Error message
INVALID_COMPANY_MONITORING_COHORT
What it means
Thrown by scheduleAccountWorkHandler when the deduplicated requestedCompanyIds list is empty or longer than COMPANY_MONITORING_SCAN_COHORT_LIMIT (25). Scan cohorts are bounded so a single work item's obligations and provider query stay within lease and budget limits.
Source
Thrown at convex/companyMonitoring/orchestration.ts:294
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),
)
.unique(),
ctx.db
.query("companyMonitoringScanObligations")
.withIndex("by_account_company_source", (q) =>
q
.eq("ownerAccountId", args.ownerAccountId)
.eq("companyId", companyId)
.eq("source", args.source),
)View on GitHub (pinned to ffec79ac33)
Solutions
- Chunk company ids into cohorts of at most COMPANY_MONITORING_SCAN_COHORT_LIMIT (25) and schedule one work item per chunk.
- Skip scheduling when the list is empty.
- Read the limit from the exported constant rather than hard-coding 25.
Example fix
// before
await schedule({ ownerAccountId, source, companyIds: allIds });
// after
const LIMIT = 25;
for (let i = 0; i < allIds.length; i += LIMIT) {
await schedule({ ownerAccountId, source, companyIds: allIds.slice(i, i + LIMIT) });
} Defensive patterns
Strategy: validation
Validate before calling
const LIMIT = COMPANY_MONITORING_SCAN_COHORT_LIMIT;
const unique = [...new Set(companyIds)];
if (unique.length === 0 || unique.length > LIMIT) {
throw new Error("cohort out of range");
} Type guard
function cohortValid(ids: string[], limit = 25): boolean {
const u = new Set(ids);
return u.size > 0 && u.size <= limit;
} Prevention
- Chunk company ids into cohorts of COMPANY_MONITORING_SCAN_COHORT_LIMIT.
- Skip scheduling for empty lists.
When it happens
Trigger: Calling account-work scheduling with no company ids or with more than 25 distinct company ids in one call.
Common situations: A 'scan all' action that does not chunk by cohort limit; an empty list from a filter that matched nothing; cohort limit lowered after code paths were written against a larger value.
Related errors
- INVALID_COMPANY_PATCH
- COMPANY_MONITORING_EVIDENCE_SUBJECTS_INVALID
- COMPANY_MONITORING_EVIDENCE_BATCH_INVALID
- COMPANY_MONITORING_EVIDENCE_EXPANSION_INVALID
- COUNTRIES_LIMIT_EXCEEDED
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/10c7e1ae01f2f8fd.
Report an issue: GitHub.