{"record":{"id":"583139209bfb83cb","repo":"different-ai/openwork","slug":"invalid-payload-583139","errorCode":"invalid_payload","errorMessage":"operations must include <= ${FILE_SESSION_MAX_BATCH_ITEMS} items","messagePattern":"operations must include <= (.+?) items","errorType":"http","errorClass":"ApiError","httpStatus":400,"severity":"error","filePath":"apps/server/src/routes/files.ts","lineNumber":759,"sourceCode":"  });\n\n  addRoute(routes, \"POST\", \"/files/sessions/:sessionId/ops\", \"client\", async (ctx) => {\n    ensureWritable(config);\n    requireClientScope(ctx, \"collaborator\");\n    const { session, workspace } = resolveFileSession(ctx, ctx.params.sessionId);\n    if (!session.canWrite) {\n      throw new ApiError(403, \"forbidden\", \"File session is read-only\");\n    }\n\n    const body = await readJsonBody(ctx.request);\n    const operations = Array.isArray(body.operations)\n      ? (body.operations as Array<Record<string, unknown>>)\n      : null;\n    if (!operations || !operations.length) {\n      throw new ApiError(400, \"invalid_payload\", \"operations must be a non-empty array\");\n    }\n    if (operations.length > FILE_SESSION_MAX_BATCH_ITEMS) {\n      throw new ApiError(400, \"invalid_payload\", `operations must include <= ${FILE_SESSION_MAX_BATCH_ITEMS} items`);\n    }\n\n    const items: Array<Record<string, unknown>> = [];\n    const approvalPaths: string[] = [];\n    for (const op of operations) {\n      if (typeof op?.path === \"string\" && op.path.trim()) {\n        approvalPaths.push(resolveSafeChildPath(workspace.path, normalizeWorkspaceRelativePath(op.path, { allowSubdirs: true })));\n      }\n      if (typeof op?.from === \"string\" && op.from.trim()) {\n        approvalPaths.push(resolveSafeChildPath(workspace.path, normalizeWorkspaceRelativePath(op.from, { allowSubdirs: true })));\n      }\n      if (typeof op?.to === \"string\" && op.to.trim()) {\n        approvalPaths.push(resolveSafeChildPath(workspace.path, normalizeWorkspaceRelativePath(op.to, { allowSubdirs: true })));\n      }\n    }\n\n    if (approvalPaths.length) {\n      await requireApproval(ctx, {","sourceCodeStart":741,"sourceCodeEnd":777,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/routes/files.ts#L741-L777","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)"],"exampleFix":"// before\nawait api.post(`/workspace/${id}/files/batch`, { operations: allOps });\n// after\nconst MAX = 50; // FILE_SESSION_MAX_BATCH_ITEMS\nfor (let i = 0; i < allOps.length; i += MAX) {\n  await api.post(`/workspace/${id}/files/batch`, { operations: allOps.slice(i, i + MAX) });\n}","handlingStrategy":"validation","validationCode":"import { FILE_SESSION_MAX_BATCH_ITEMS } from \"@server/files\";\nconst ops = buildOperations();\nif (ops.length === 0) throw new Error(\"operations must be non-empty\");\nif (ops.length > FILE_SESSION_MAX_BATCH_ITEMS) {\n  throw new Error(`split into chunks of <= ${FILE_SESSION_MAX_BATCH_ITEMS}`);\n}","typeGuard":"function isValidBatch(ops: unknown): ops is Array<Record<string, unknown>> {\n  return Array.isArray(ops) && ops.length > 0 && ops.length <= FILE_SESSION_MAX_BATCH_ITEMS;\n}","tryCatchPattern":"try {\n  await api.post(`/workspace/${id}/files/batch`, { operations: chunk });\n} catch (e) {\n  if (e.code === \"invalid_payload\" && /<= \\d+ items/.test(e.message)) {\n    for (const c of toChunks(ops, FILE_SESSION_MAX_BATCH_ITEMS)) await api.post(`...`, { operations: c });\n    return;\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["http-400","validation","batch-limit","payload"],"backgroundTag":"payload-size-limit-exceeded","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}