koala73/worldmonitor · error · Error
batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportBatchByte
Error message
batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportBatchBytes} bytes What it means
Thrown by normalizeCompanyImportBatch() when the UTF-8 byte size of JSON.stringify(normalized) exceeds COMPANY_MONITORING_LIMITS.maxImportBatchBytes (256 KiB). This is the last batch guard: even a row-count-legal batch (<=100 rows) can exceed the serialized byte cap, since each individual row may carry up to maxImportRowBytes (8 KiB).
Source
Thrown at shared/company-monitoring-contract.ts:322
return normalized;
}
export function normalizeCompanyImportBatch(inputs: CompanyImportRowInput[]): NormalizedCompanyImportRow[] {
if (!Array.isArray(inputs)) throw new Error('import batch must be a list');
if (inputs.length === 0) throw new Error('import batch requires at least one row');
if (inputs.length > COMPANY_MONITORING_LIMITS.maxImportRows) {
throw new Error(`batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportRows} rows`);
}
const normalized = inputs.map(normalizeCompanyImportRow).sort((left, right) => left.ordinal - right.ordinal);
const importId = normalized[0]?.clientImportId;
for (let index = 0; index < normalized.length; index += 1) {
const row = normalized[index]!;
if (row.clientImportId !== importId) throw new Error('batch rows must share one clientImportId');
if (row.ordinal !== index) throw new Error('batch ordinals must be contiguous from 0');
}
if (utf8Bytes(JSON.stringify(normalized)) > COMPANY_MONITORING_LIMITS.maxImportBatchBytes) {
throw new Error(`batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportBatchBytes} bytes`);
}
return normalized;
}
export function assertCompanyMonitoringPayloadSize(byteLength: number): void {
if (!Number.isSafeInteger(byteLength) || byteLength < 0) throw new Error('request byte length is invalid');
if (byteLength > COMPANY_MONITORING_LIMITS.maxRequestBytes) {
throw new Error(`request exceeds ${COMPANY_MONITORING_LIMITS.maxRequestBytes} bytes`);
}
}
export function assertCompanyMonitoringAccountContext(
context: { ownerAccountId?: string } | null | undefined,
): string {
const ownerAccountId = context?.ownerAccountId;
if (typeof ownerAccountId !== 'string' || !ownerAccountId.trim()) {
throw new Error('account context is required');
}View on GitHub (pinned to eeab0a219f)
Solutions
- Reduce the number of rows per batch — with worst-case 8 KiB rows, roughly 30 rows keeps you under 256 KiB; compute your own safe chunk size from measured row sizes
- Trim or truncate large text fields before import instead of shipping full documents inside rows
- Pre-measure with the same method the contract uses: new TextEncoder().encode(JSON.stringify(rows)).length > 262144, and split before sending
Example fix
// before
await submitImportBatch(normalizeCompanyImportBatch(allRows)); // 90 rows x ~6 KiB -> >256 KiB, throws
// after
const enc = new TextEncoder();
const MAX = COMPANY_MONITORING_LIMITS.maxImportBatchBytes;
let chunk: Row[] = [];
for (const row of allRows) {
const next = [...chunk, row];
if (enc.encode(JSON.stringify(next)).length > MAX) {
await submitImportBatch(normalizeCompanyImportBatch(chunk));
chunk = [];
}
chunk.push(row);
}
if (chunk.length) await submitImportBatch(normalizeCompanyImportBatch(chunk)); Defensive patterns
Strategy: validation
Validate before calling
const enc = new TextEncoder();
const MAX_BATCH = COMPANY_MONITORING_LIMITS.maxImportBatchBytes; // 256 KiB
function fitsBatchBudget(rows: unknown[]): boolean {
return enc.encode(JSON.stringify(rows)).length <= MAX_BATCH;
}
// chunk by measured bytes, not row count alone:
function chunkByBytes<T>(rows: T[]): T[][] { /* grow chunk until fitsBatchBudget fails, then flush */ } Try / catch
catch (e) { if (e instanceof Error && e.message.endsWith(' bytes')) { shrinkBatchAndRetry(); } else throw e; } Prevention
- Measure with TextEncoder on the serialized JSON — string .length undercounts UTF-8, especially CJK/emoji
- Cap free-text fields at ingestion, not at submit
- Assume worst-case rows (8 KiB each) when picking a client-side chunk size
When it happens
Trigger: 100 rows each near the 8 KiB per-row cap (100 x 8 KiB = 800 KiB >> 256 KiB); rows with long free-text fields (notes, descriptions, evidence text) that inflate the serialized form; Unicode content whose UTF-8 encoding is much larger than the JS string length suggests.
Common situations: A CSV import with a large notes column; importers that copy full article/evidence text into each row; CJK or emoji-heavy content measured with .length (UTF-16 code units) instead of byte length during client-side estimation.
Related errors
- batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportRows} row
- batch rows must share one clientImportId
- batch ordinals must be contiguous from 0
- invalid ${kind} logical ID
- get_intel_timeline requires at least one of domain ("conflic
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/afb9c999e246529b.
Report an issue: GitHub.