danny-avila/LibreChat · error · Error

Download failed: ${response.status} ${response.statusText}

Error message

Download failed: ${response.status} ${response.statusText}

What it means

Thrown by the single-file SharePoint download mutation when Microsoft Graph returns a non-2xx response for the file-content request. The download URL is either a pre-issued `file.downloadUrl` or a constructed Graph endpoint `https://graph.microsoft.com/v1.0/drives/{driveId}/items/{itemId}/content`, always called with `Authorization: Bearer {accessToken}`. The thrown message includes the HTTP status and statusText so the caller can distinguish 401/403/404.

Source

Thrown at client/src/data-provider/Files/sharepoint.ts:52

    file: SharePointFile;
    accessToken: string;
    onProgress?: (progress: SharePointDownloadProgress) => void;
  }
> => {
  return useMutation({
    mutationFn: async ({ file, accessToken, onProgress }) => {
      const downloadUrl =
        file.downloadUrl ||
        `https://graph.microsoft.com/v1.0/drives/${file.driveId}/items/${file.itemId}/content`;

      const response = await fetch(downloadUrl, {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      });

      if (!response.ok) {
        throw new Error(`Download failed: ${response.status} ${response.statusText}`);
      }

      const contentLength = parseInt(response.headers.get('content-length') || '0');
      const reader = response.body?.getReader();
      if (!reader) {
        throw new Error('Failed to get response reader');
      }

      const chunks: Uint8Array[] = [];
      let receivedLength = 0;

      while (true) {
        const { done, value } = await reader.read();

        if (done) break;

        chunks.push(value);
        receivedLength += value.length;

View on GitHub (pinned to 5ff282f900)

Solutions

  1. Inspect the captured `response.status`/`statusText` in the thrown message — 401/403 means token/scope, 404 means the item is gone, 429/503 means retry with backoff.
  2. For 401/403, force a token refresh (the hook already re-fetches via `refetchToken`) and confirm the Download scope (`sharePointPickerGraphScope`) includes a Files.Read permission.
  3. For 404, re-open the SharePoint picker so a fresh `downloadUrl`/`driveId`/`itemId` is resolved.
  4. For 429/503, retry with exponential backoff and respect the `Retry-After` header.

Example fix

// before
if (!response.ok) {
  throw new Error(`Download failed: ${response.status} ${response.statusText}`);
}
// after — include item identity + surface retry guidance for transient errors
if (!response.ok) {
  const detail = `${response.status} ${response.statusText}`;
  const err = new Error(`Download failed for ${file.driveId}/${file.itemId}: ${detail}`);
  err.status = response.status;
  err.retryable = response.status === 429 || response.status >= 500;
  throw err;
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate the file descriptor has the minimum fields for a Graph content request
function isValidSharePointFile(f: SharePointFile): boolean {
  return Boolean((f.downloadUrl || (f.driveId && f.itemId)) && f.name);
}

Type guard

function isSharePointFile(f: unknown): f is SharePointFile {
  return typeof f === 'object' && f !== null && typeof (f as any).name === 'string' &&
    (typeof (f as any).downloadUrl === 'string' ||
      (typeof (f as any).driveId === 'string' && typeof (f as any).itemId === 'string'));
}

Try / catch

try {
  const response = await fetch(downloadUrl, { headers: { Authorization: `Bearer ${accessToken}` } });
  if (!response.ok) throw Object.assign(new Error(`Download failed: ${response.status}`), { status: response.status, retryable: response.status === 429 || response.status >= 500 });
} catch (err) {
  if (err.status === 401) { await refetchToken(); /* retry once */ }
  else throw err;
}

Prevention

When it happens

Trigger: The Graph access token is expired or lacks the `Files.Read`/`Files.Read.All` scope (401/403); the drive item was deleted, moved, or the `driveId`/`itemId` are stale (404); the token is for a different tenant/user than the resource owner (403); Graph throttling returns 429/503.

Common situations: Token cached for 50 minutes but Graph rejected it (clock skew or early expiry); user selected a file in the SharePoint picker then lost access before download; SharePoint item moved/renamed after the picker resolved it; wrong Graph scope requested for Download vs Pick (the scope selection differs by `purpose`).

Related errors


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/b2a9e92783b9ca9b. Report an issue: GitHub.