abhigyanpatwari/GitNexus · error · BadRequestError

Too many directories in upload

Error message

Too many directories in upload

What it means

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'.

Source

Thrown at gitnexus/src/server/upload-ingest.ts:137

  const segs = relParent.split(path.sep).filter(Boolean);
  let cur = stageRoot;
  for (const seg of segs) {
    cur = path.join(cur, seg);
    let made = false;
    try {
      fs.mkdirSync(cur);
      made = true;
    } catch (err) {
      if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;
    }
    const st = fs.lstatSync(cur);
    if (st.isSymbolicLink() || !st.isDirectory()) {
      throw new BadRequestError('Upload path escapes the sandbox');
    }
    if (made) {
      state.dirCount++;
      if (state.dirCount > state.limits.maxDirs) {
        throw new BadRequestError('Too many directories in upload', 413);
      }
    }
  }
}

export interface IngestOptions {
  /** Override the staging parent dir (defaults to UPLOAD_ROOT; for tests). */
  root?: string;
}

/**
 * Parse and securely write a multipart folder upload into a fresh staging
 * directory under UPLOAD_ROOT. Resolves with the populated staging dir, or
 * rejects with a BadRequestError (status 400/413) after removing the staging
 * dir. The caller owns promotion + cleanup of the returned `stageRoot`.
 */
export async function ingestUpload(
  req: IncomingMessage,

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Exclude heavy generated trees (node_modules, build output, .venv) from the upload so distinct directory count stays under 50,000
  2. If the upload is legitimately that large, raise limits.maxDirs (and maxTotalBytes) in the UploadLimits object passed to the ingest handler
  3. Pre-validate client-side: count distinct parent directories of the selected files before POSTing and split or trim the upload
  4. If you operate the server, put the upload endpoint behind auth/rate limits so strangers cannot force 413 churn

Example fix

// before
const limits = { ...DEFAULT_UPLOAD_LIMITS }; // maxDirs: 50000

// after — allow very large trees knowingly
const limits = {
  ...DEFAULT_UPLOAD_LIMITS,
  maxDirs: 200_000,
  maxTotalBytes: 1024 * 1024 * 1024,
};
Defensive patterns

Strategy: validation

Validate before calling

function countDistinctDirs(files: { relativePath: string }[]): number {
  const dirs = new Set<string>();
  for (const f of files) {
    const parts = f.relativePath.split('/').filter(Boolean);
    for (let i = 1; i < parts.length; i++) dirs.add(parts.slice(0, i).join('/'));
  }
  return dirs.size;
}
if (countDistinctDirs(files) > 50_000) {
  throw new Error('Too many directories — trim the folder or split the upload');
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20). Data as JSON: /api/errors/22cb98d6e8301c82. Report an issue: GitHub.