abhigyanpatwari/GitNexus · error · BadRequestError
Upload must be a folder
Error message
Upload must be a folder
What it means
The upload handler joins stageRoot with the manifest's topLevelName and stats it; webkitRelativePath-based folder uploads always prefix entries with the picked folder, so the joined path must be a directory. If stat fails or reports a non-directory, the request is rejected with BadRequestError 400 'Upload must be a folder' BEFORE taking the single analysis slot, so a malformed upload cannot wedge the server.
Source
Thrown at gitnexus/src/server/analyze-upload.ts:96
const baseName = deriveUploadName(result.topLevelName);
if (!baseName) {
throw new BadRequestError('Uploaded folder has no usable name');
}
// webkitRelativePath prefixes every entry with the picked folder, so the
// real repo root is stageRoot/<topLevelName>. Validate it is a directory
// BEFORE taking the single analysis slot — a malformed (non-folder)
// upload must not be able to occupy the slot.
const innerRoot = path.join(result.stageRoot, result.topLevelName);
let innerIsDir = false;
try {
innerIsDir = (await fsp.stat(innerRoot)).isDirectory();
} catch {
innerIsDir = false;
}
if (!innerIsDir) {
throw new BadRequestError('Upload must be a folder');
}
const finalName = await pickAvailableName(baseName);
const finalDir = getUploadDir(finalName);
// createJob occupies the single analysis slot (throws 'already in
// progress' → 409). From here on, ANY error before launch MUST release
// the slot via failJob in the catch, or the server wedges all analyses.
let job: UploadJobRef;
try {
job = deps.createJob({ repoPath: finalDir });
} catch (err) {
const msg = err instanceof Error ? err.message : '';
if (msg.includes('already in progress')) {
throw new BadRequestError(msg, 409);
}
throw err;
}View on GitHub (pinned to aac7515d2a)
Solutions
- Use <input type='file' webkitdirectory> (folder picker) so every entry's relative path starts with the folder name
- Ensure custom upload clients keep the top-level folder prefix on every part's path
- Upload a directory, not individual files
Example fix
<!-- before --> <input type="file" id="f"> <!-- after --> <input type="file" id="f" webkitdirectory>
Defensive patterns
Strategy: validation
Validate before calling
function uploadLooksLikeFolder(files) {
return files.length > 0 && files.every((f) => (f.webkitRelativePath || '').includes('/'));
} Type guard
function isFolderUpload(fileList) {
return [...fileList].every((f) => typeof f.webkitRelativePath === 'string' && f.webkitRelativePath.split('/').length >= 2);
} Try / catch
try { await postUpload(formData); }
catch (e) {
if (e.status === 400 && e.message === 'Upload must be a folder') showHint('Select a folder, not individual files');
else throw e;
} Prevention
- Use <input type='file' webkitdirectory> so the browser prefixes every entry with the folder
- In custom clients, always write the top-level folder as the first path segment of each part
- Disable the submit button until isFolderUpload(files) is true
When it happens
Trigger: POST /api/analyze/upload whose multipart manifest's top-level entry is a file: a drag-dropped single file, or a custom client that posts entries without the folder prefix the browser's folder picker would add.
Common situations: Using a plain <input type='file'> instead of one with the webkitdirectory attribute, so relative paths lack a folder root; hand-rolled upload clients that strip the top-level folder from paths; drag-and-drop of a file rather than a folder.
Related errors
- Uploaded folder has no usable name
- Invalid upload path
- Upload must be a single folder of files
- Upload path too long
- Upload path must not contain traversal segments
AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20).
Data as JSON: /api/errors/b306c93bff52a170.
Report an issue: GitHub.