different-ai/openwork · error · ApiError
invalid_payload
invalid_payload
Error message
operations must include <= ${FILE_SESSION_MAX_BATCH_ITEMS} items What it means
The file batch endpoint validates that the operations array is non-empty and does not exceed FILE_SESSION_MAX_BATCH_ITEMS; oversized batches are rejected with a 400 invalid_payload ApiError before any operation is processed. This caps batch size to protect the server and file-session approval flow from unbounded work.
Source
Thrown at apps/server/src/routes/files.ts:759
});
addRoute(routes, "POST", "/files/sessions/:sessionId/ops", "client", async (ctx) => {
ensureWritable(config);
requireClientScope(ctx, "collaborator");
const { session, workspace } = resolveFileSession(ctx, ctx.params.sessionId);
if (!session.canWrite) {
throw new ApiError(403, "forbidden", "File session is read-only");
}
const body = await readJsonBody(ctx.request);
const operations = Array.isArray(body.operations)
? (body.operations as Array<Record<string, unknown>>)
: null;
if (!operations || !operations.length) {
throw new ApiError(400, "invalid_payload", "operations must be a non-empty array");
}
if (operations.length > FILE_SESSION_MAX_BATCH_ITEMS) {
throw new ApiError(400, "invalid_payload", `operations must include <= ${FILE_SESSION_MAX_BATCH_ITEMS} items`);
}
const items: Array<Record<string, unknown>> = [];
const approvalPaths: string[] = [];
for (const op of operations) {
if (typeof op?.path === "string" && op.path.trim()) {
approvalPaths.push(resolveSafeChildPath(workspace.path, normalizeWorkspaceRelativePath(op.path, { allowSubdirs: true })));
}
if (typeof op?.from === "string" && op.from.trim()) {
approvalPaths.push(resolveSafeChildPath(workspace.path, normalizeWorkspaceRelativePath(op.from, { allowSubdirs: true })));
}
if (typeof op?.to === "string" && op.to.trim()) {
approvalPaths.push(resolveSafeChildPath(workspace.path, normalizeWorkspaceRelativePath(op.to, { allowSubdirs: true })));
}
}
if (approvalPaths.length) {
await requireApproval(ctx, {View on GitHub (pinned to 2b7df46e8a)
Solutions
- Split the operations array into chunks of at most FILE_SESSION_MAX_BATCH_ITEMS and issue one request per chunk
- Check the error message/constant for the exact cap before batching
- Reduce batch size by grouping only related operations per session
- Contact the operator if a larger cap is legitimately needed (server-side constant)
Example fix
// before
await api.post(`/workspace/${id}/files/batch`, { operations: allOps });
// after
const MAX = 50; // FILE_SESSION_MAX_BATCH_ITEMS
for (let i = 0; i < allOps.length; i += MAX) {
await api.post(`/workspace/${id}/files/batch`, { operations: allOps.slice(i, i + MAX) });
} Defensive patterns
Strategy: validation
Validate before calling
import { FILE_SESSION_MAX_BATCH_ITEMS } from "@server/files";
const ops = buildOperations();
if (ops.length === 0) throw new Error("operations must be non-empty");
if (ops.length > FILE_SESSION_MAX_BATCH_ITEMS) {
throw new Error(`split into chunks of <= ${FILE_SESSION_MAX_BATCH_ITEMS}`);
} Type guard
function isValidBatch(ops: unknown): ops is Array<Record<string, unknown>> {
return Array.isArray(ops) && ops.length > 0 && ops.length <= FILE_SESSION_MAX_BATCH_ITEMS;
} Try / catch
try {
await api.post(`/workspace/${id}/files/batch`, { operations: chunk });
} catch (e) {
if (e.code === "invalid_payload" && /<= \d+ items/.test(e.message)) {
for (const c of toChunks(ops, FILE_SESSION_MAX_BATCH_ITEMS)) await api.post(`...`, { operations: c });
return;
}
throw e;
} Prevention
- Chunk operation arrays to the documented maximum before every batch call
- Keep batch generators aware of FILE_SESSION_MAX_BATCH_ITEMS
- Log batch size in clients so oversize submissions are visible
- Prefer several small batch sessions over one giant request
When it happens
Trigger: POSTing a batch file-session payload whose operations array length is greater than FILE_SESSION_MAX_BATCH_ITEMS; also triggered when operations is missing/null or empty (same code, different message).
Common situations: A script generating a large refactor submits hundreds of file writes in one request; a migration tool dumps all its work into a single call; a client ignores pagination and batches an entire directory tree.
Related errors
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/583139209bfb83cb.
Report an issue: GitHub.