abhigyanpatwari/GitNexus · error

Upload failed

Error message

Upload failed

What it means

Generic HTTP 500 from POST of a folder upload (analyze-upload handler) when something failed that is not a BadRequestError. The handler streams a multipart upload to a staging dir, validates it is a folder, occupies the single analysis slot via createJob (409 if busy), promotes staging with an atomic rename, removes any carried .gitnexus index, and launches analysis. All expected client mistakes (no usable name, upload not a folder, 409 already-in-progress) surface as their own 4xx via BadRequestError; this 500 means an infrastructure-level failure — the upload stream broke, or a filesystem operation (mkdir/rename under UPLOAD_ROOT) failed. The catch also releases the job slot and deletes staging/promoted dirs, so retries start clean.

Source

Thrown at gitnexus/src/server/analyze-upload.ts:152

      res.status(202).json({ jobId: job.id, status: job.status });
    } catch (err) {
      // Release the single analysis slot if a job was created but never
      // launched — otherwise the leaked queued job blocks all future analyses.
      if (createdJobId && !launched) {
        deps.failJob(createdJobId, err instanceof Error ? err.message : 'Upload failed');
      }
      if (stageRoot) {
        await fsp.rm(stageRoot, { recursive: true, force: true }).catch(() => {});
      }
      if (promotedDir && !launched) {
        await fsp.rm(promotedDir, { recursive: true, force: true }).catch(() => {});
      }
      if (err instanceof BadRequestError) {
        res.status(err.status).json({ error: err.message });
        return;
      }
      res.status(500).json({ error: 'Upload failed' });
    }
  };
}

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Check the server stderr/log for the original exception — the 500 envelope deliberately hides it
  2. Verify the client is uploading a folder (multipart with webkitRelativePath entries, one top-level directory), since the 400 'Upload must be a folder' path covers only the cleanly-detected case
  3. Confirm UPLOAD_ROOT is writable and the volume has free space; check reverse-proxy body-size and timeout limits for large uploads
  4. Retry the upload — the handler cleans up staging and releases the analysis slot on failure, so a retry is always safe
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the folder selection before starting the upload.
function assertFolderUpload(files: File[]): void {
  const roots = new Set(files.map((f) => (f as File & { webkitRelativePath?: string }).webkitRelativePath?.split('/')[0] ?? ''));
  if (roots.size === 0 || roots.has('')) throw new Error('select a folder (webkitdirectory), not loose files');
}

Try / catch

try {
  const res = await uploadFolder(form);
  if (res.status === 500 && (await res.json()).error === 'Upload failed') {
    showTransientError('Upload failed server-side — check server logs; your slot was released, safe to retry');
    return retryUploadOnce();
  }
  if (res.status === 409) { await waitForActiveJob(); return uploadFolder(form); } // never auto-spam 409
  return res;
} catch (e) {
  reportUploadError(e); // network interruption mid-multipart also lands here client-side
}

Prevention

When it happens

Trigger: Network interruption mid-multipart-upload making ingest throw; disk full or permission denied when creating UPLOAD_ROOT or renaming stageRoot/<topLevelName> to the final dir; the deps.launch step throwing before the 202 is sent; busboy/parser errors on a truncated request body.

Common situations: Uploading very large folders over flaky connections; UPLOAD_ROOT on a volume that is full or read-only; a server-side path change between staging and final location; reverse proxy cutting off long uploads at a timeout (nginx client_max_body_size / proxy_read_timeout).

Related errors


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