koala73/worldmonitor · error · ApiError
Nested batch requests are not allowed
Error message
Nested batch requests are not allowed
What it means
executeBatch (POST /api/batch/v1/execute) refuses any inbound request that already carries the x-wm-batch marker header. Sub-requests dispatched by a batch get this header set by buildSubRequestHeaders, and the gateway forwards it untouched, so its presence proves the request was issued by another batch — batching a batch would multiply per-request resource use recursively. It is an ApiError 400 raised before any operation validation.
Source
Thrown at server/worldmonitor/batch/v1/execute-batch.ts:194
} catch {
return { id: op.id, status: response.status, error: 'invalid_json' };
}
return { id: op.id, status: response.status, body: body as BatchOperationBody, error: '' };
}
export function createExecuteBatch(
fetchImpl: FetchLike = (input, init) => fetch(input, init),
) {
return async function executeBatch(
ctx: ServerContext,
req: ExecuteBatchRequest,
): Promise<ExecuteBatchResponse> {
// Recursion guard: the gateway forwards the marker untouched, so a batch
// arriving with it was issued BY a batch — refuse regardless of the
// per-path nested_batch check below.
if (ctx.request.headers.has(BATCH_MARKER_HEADER)) {
throw new ApiError(400, 'Nested batch requests are not allowed', '');
}
const operations = Array.isArray(req.operations) ? req.operations : [];
if (operations.length < 1 || operations.length > MAX_BATCH_OPERATIONS) {
throw new ValidationError([{
field: 'operations',
description: `must contain between 1 and ${MAX_BATCH_OPERATIONS} operations`,
}]);
}
const origin = new URL(ctx.request.url).origin;
const { validated, violations } = validateOperations(operations, origin);
if (violations.length > 0) {
throw new ValidationError(violations);
}
const headers = buildSubRequestHeaders(ctx.request.headers);
const results = await Promise.all(View on GitHub (pinned to eeab0a219f)
Solutions
- Stop forwarding the x-wm-batch header in your HTTP client — only the server sets it on sub-requests
- If you intended to run multiple batches, issue separate top-level POST /api/batch/v1/execute requests instead of nesting
- If you meant to include another endpoint in a batch, use its documented /api/<domain>/v<N>/<rpc> path; it will run as a normal operation
Example fix
// before — headers object reused from a prior (server-dispatched) request
const resp = await fetch(`${origin}/api/batch/v1/execute`, {
method: 'POST',
headers: capturedHeaders, // contains x-wm-batch: 1 -> 400
body: JSON.stringify({ operations }),
});
// after — explicit allowlist of client headers only
const resp = await fetch(`${origin}/api/batch/v1/execute`, {
method: 'POST',
headers: { authorization: capturedHeaders.authorization, 'content-type': 'application/json' },
body: JSON.stringify({ operations }),
}); Defensive patterns
Strategy: validation
Validate before calling
// strip any batch marker and batch paths before submitting
const safeHeaders = new Headers(myHeaders); // never copy x-wm-batch from prior traffic
safeHeaders.delete('x-wm-batch');
const operations = ops.filter((o) => !o.path.startsWith('/api/batch/')); Type guard
function isNestedBatchError(body: unknown): boolean {
return typeof body === 'object' && body !== null && 'message' in body
&& (body as { message?: string }).message === 'Nested batch requests are not allowed';
} Try / catch
try {
await post('/api/batch/v1/execute', { operations });
} catch (e) {
if (e instanceof HttpError && e.status === 400 && isNestedBatchError(e.body)) {
// fix the client's header forwarding; retrying unchanged will fail again
}
} Prevention
- Use an explicit header allowlist (authorization, content-type) in your HTTP client instead of forwarding everything
- Never include /api/batch/* paths in operations — they fail per-operation as 'nested_batch' anyway
- In test harnesses, build headers from scratch rather than replaying recorded request headers
When it happens
Trigger: A client (or test harness) that copies response/request headers wholesale from a previous batch call onto a new top-level batch request, including x-wm-batch: 1; a proxy that injects unknown x-* headers; manually curling the batch endpoint with -H 'x-wm-batch: 1'. Note: including a /api/batch/* path inside operations does NOT normally reach this throw — validateOperations marks that operation as a per-result error 'nested_batch' instead.
Common situations: SDK or fetch wrapper with default headers captured from an earlier sub-request; an integration test replaying recorded headers; an agent framework that forwards all inbound headers when proxying the API.
Related errors
- must contain between 1 and ${MAX_BATCH_OPERATIONS} operation
- must be at most ${MAX_OPERATION_ID_LENGTH} characters
- duplicate id "${id}" — results would be ambiguous
- get_intel_timeline requires at least one of domain ("conflic
- INCOMPATIBLE_DELIVERY
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/847dc78ba3e49826.
Report an issue: GitHub.