koala73/worldmonitor · error · ConvexError

COMPANY_MONITORING_EVIDENCE_BATCH_INVALID

Error message

COMPANY_MONITORING_EVIDENCE_BATCH_INVALID

What it means

Thrown by ingestCompanyEvidenceForCompanyIds when the input.evidence array is empty or larger than MAX_INGESTION_ROWS (100). This bounds the per-call ingestion size so each mutation stays within its scheduler budget; an empty batch is rejected because there is nothing to ingest.

Source

Thrown at convex/companyMonitoring/evidence.ts:364

    referenceEvidenceFingerprints: [],
    referenceCount: 0,
    referencesTruncated: false,
    evidenceRevision: existing.evidenceRevision + 1,
    evidenceSnapshotDigest: emptyEvidenceSnapshotDigest,
    updatedAt: now,
  });
}

export async function ingestCompanyEvidenceForCompanyIds(
  ctx: MutationCtx,
  input: {
    ownerAccountId: string;
    companyIds: string[];
    evidence: ProviderEvidence[];
  },
) {
  if (input.evidence.length === 0 || input.evidence.length > MAX_INGESTION_ROWS) {
    throw new ConvexError("COMPANY_MONITORING_EVIDENCE_BATCH_INVALID");
  }
  const account = await ctx.db
    .query("companyMonitoringAccounts")
    .withIndex("by_logicalAccountId", (q) => q.eq("logicalAccountId", input.ownerAccountId))
    .unique();
  if (!account || account.lifecycle !== "entitled" || account.terminalReason) {
    throw new ConvexError("COMPANY_MONITORING_ACCOUNT_INACTIVE");
  }
  const subjects = await canonicalSubjects(ctx, input.ownerAccountId, input.companyIds);
  const normalized = await normalizeCompanyEvidence({
    ownerAccountId: input.ownerAccountId,
    subjects,
    evidence: input.evidence,
    now: Date.now(),
  });
  if (normalized.evidence.length > MAX_EXPANDED_EVIDENCE_ROWS) {
    throw new ConvexError("COMPANY_MONITORING_EVIDENCE_EXPANSION_INVALID");
  }

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Chunk provider evidence into batches of at most MAX_INGESTION_ROWS before calling ingest.
  2. Skip the call entirely when the evidence list is empty.
  3. Re-read MAX_INGESTION_ROWS from the module rather than hard-coding 100.

Example fix

// before
await ingest({ ownerAccountId, companyIds, evidence: allEvidence });

// after
const BATCH = 100;
for (let i = 0; i < allEvidence.length; i += BATCH) {
  await ingest({ ownerAccountId, companyIds, evidence: allEvidence.slice(i, i + BATCH) });
}
Defensive patterns

Strategy: validation

Validate before calling

if (evidence.length === 0) return { status: "noop" };
if (evidence.length > 100) throw new Error("evidence batch exceeds 100 rows");
await ingest(...);

Type guard

function batchWithinLimit(rows: unknown[], limit = 100): boolean {
  return rows.length > 0 && rows.length <= limit;
}

Prevention

When it happens

Trigger: Calling ingestion with evidence.length === 0 or evidence.length > 100 (MAX_INGESTION_ROWS).

Common situations: Worker batching a full Exa/X receipt that exceeds 100 rows without chunking; a no-op call with an empty evidence list; a normalization bug collapsing all rows away.

Related errors


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