{"record":{"id":"22cb98d6e8301c82","repo":"abhigyanpatwari/GitNexus","slug":"too-many-directories-in-upload","errorCode":null,"errorMessage":"Too many directories in upload","messagePattern":"Too many directories in upload","errorType":"http","errorClass":"BadRequestError","httpStatus":413,"severity":"error","filePath":"gitnexus/src/server/upload-ingest.ts","lineNumber":137,"sourceCode":"  const segs = relParent.split(path.sep).filter(Boolean);\n  let cur = stageRoot;\n  for (const seg of segs) {\n    cur = path.join(cur, seg);\n    let made = false;\n    try {\n      fs.mkdirSync(cur);\n      made = true;\n    } catch (err) {\n      if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n    }\n    const st = fs.lstatSync(cur);\n    if (st.isSymbolicLink() || !st.isDirectory()) {\n      throw new BadRequestError('Upload path escapes the sandbox');\n    }\n    if (made) {\n      state.dirCount++;\n      if (state.dirCount > state.limits.maxDirs) {\n        throw new BadRequestError('Too many directories in upload', 413);\n      }\n    }\n  }\n}\n\nexport interface IngestOptions {\n  /** Override the staging parent dir (defaults to UPLOAD_ROOT; for tests). */\n  root?: string;\n}\n\n/**\n * Parse and securely write a multipart folder upload into a fresh staging\n * directory under UPLOAD_ROOT. Resolves with the populated staging dir, or\n * rejects with a BadRequestError (status 400/413) after removing the staging\n * dir. The caller owns promotion + cleanup of the returned `stageRoot`.\n */\nexport async function ingestUpload(\n  req: IncomingMessage,","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/upload-ingest.ts#L119-L155","documentation":"Thrown by the multipart folder-upload ingest pipeline when the number of directories it materializes inside the staging sandbox exceeds UploadLimits.maxDirs (default 50,000). Every uploaded file's parent path is created via mkdirContained (mkdir -p semantics), and each newly created directory increments a counter; crossing the limit aborts the upload with HTTP 413. It is the directory-count sibling of the parallel maxTotalBytes (250 MB default) byte cap, guarding against inode/disk exhaustion from a directory 'zip bomb'.","triggerScenarios":"POSTing a multipart folder upload to the ingest endpoint where the cumulative count of distinct newly created parent directories exceeds maxDirs — e.g. a tree with more than 50,000 distinct folders, or many files whose relative paths each introduce unique deep parent chains.","commonSituations":"Uploading node_modules or a vendored toolchain along with the project; uploading a repo mirror/backup full of generated directories; a deliberately crafted multipart payload trying to exhaust the staging filesystem; tests that pass a small custom limits object and forget to scale it.","solutions":["Exclude heavy generated trees (node_modules, build output, .venv) from the upload so distinct directory count stays under 50,000","If the upload is legitimately that large, raise limits.maxDirs (and maxTotalBytes) in the UploadLimits object passed to the ingest handler","Pre-validate client-side: count distinct parent directories of the selected files before POSTing and split or trim the upload","If you operate the server, put the upload endpoint behind auth/rate limits so strangers cannot force 413 churn"],"exampleFix":"// before\nconst limits = { ...DEFAULT_UPLOAD_LIMITS }; // maxDirs: 50000\n\n// after — allow very large trees knowingly\nconst limits = {\n  ...DEFAULT_UPLOAD_LIMITS,\n  maxDirs: 200_000,\n  maxTotalBytes: 1024 * 1024 * 1024,\n};","handlingStrategy":"validation","validationCode":"function countDistinctDirs(files: { relativePath: string }[]): number {\n  const dirs = new Set<string>();\n  for (const f of files) {\n    const parts = f.relativePath.split('/').filter(Boolean);\n    for (let i = 1; i < parts.length; i++) dirs.add(parts.slice(0, i).join('/'));\n  }\n  return dirs.size;\n}\nif (countDistinctDirs(files) > 50_000) {\n  throw new Error('Too many directories — trim the folder or split the upload');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Count distinct parent directories client-side before POSTing and compare against the 50,000 default","Strip node_modules and build artifacts from folder uploads","Treat repeated 413s from this endpoint as a signal to split the upload, not to retry it unchanged"],"tags":["upload","multipart","resource-exhaustion","limits","http-413"],"backgroundTag":"resource-limit-exceeded","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T14:17:55.899Z"}