Tencent/WeKnora · error

download failed with status %d

Error message

download failed with status %d

What it means

DownloadFile treats any non-200 response from the attachment host as a failure and records 'download failed with status %d'. 5xx statuses are retried up to maxRetries with exponential backoff (1s, 2s, ...); 4xx and persistent 5xx statuses break out and the error surfaces wrapped as 'download file: download failed with status N'. S3 signed URLs are the usual target, so 403 (expired/invalid signature) and 404 (deleted file) are the dominant cases.

Source

Thrown at internal/datasource/connector/notion/client.go:406

	}

	var lastErr error
	for attempt := 0; attempt <= maxRetries; attempt++ {
		resp, err := c.httpClient.Do(req)
		if err != nil {
			lastErr = err
			if attempt < maxRetries {
				if sErr := sleepWithContext(ctx, time.Duration(1<<attempt)*time.Second); sErr != nil {
					return nil, sErr
				}
				continue
			}
			break
		}

		if resp.StatusCode != http.StatusOK {
			resp.Body.Close()
			lastErr = fmt.Errorf("download failed with status %d", resp.StatusCode)
			if resp.StatusCode >= 500 && attempt < maxRetries {
				if sErr := sleepWithContext(ctx, time.Duration(1<<attempt)*time.Second); sErr != nil {
					return nil, sErr
				}
				continue
			}
			break
		}

		data, err := io.ReadAll(io.LimitReader(resp.Body, maxDownloadSize+1))
		resp.Body.Close()
		if err != nil {
			return nil, err
		}
		if int64(len(data)) > maxDownloadSize {
			return nil, fmt.Errorf("file exceeds maximum download size (%d MB)", maxDownloadSize/(1024*1024))
		}
		return data, nil

View on GitHub (pinned to 988cbb0330)

Solutions

  1. For 403, re-fetch the block via ResolveBlock to obtain a fresh signed URL, then retry the download immediately.
  2. For 404, check whether the attachment still exists on the page in Notion; remove it from the index or flag the record as stale.
  3. For 5xx after retries, re-run the sync later — the retry loop already applies exponential backoff, so adding retries in the caller rarely helps.
  4. Shorten the gap between URL retrieval and download so signed URLs are used within their 1-hour validity window.

Example fix

// before
block, _ := client.ResolveBlock(ctx, blockID) // fetched hours earlier, signed URL expired
// after
block, _ := client.ResolveBlock(ctx, blockID) // re-resolve right before download
data, err := client.DownloadFile(ctx, block.File.File.URL) // fresh 1-hour signed URL
Defensive patterns

Strategy: retry

Validate before calling

// signed URLs expire after ~1 hour; check age before downloading
if time.Since(block.FetchedAt) > 45*time.Minute {
    block, err = client.ResolveBlock(ctx, block.ID)
    if err != nil {
        return err
    }
}

Type guard

func isDownloadStatusError(err error, statuses ...int) bool {
    if err == nil {
        return false
    }
    for _, s := range statuses {
        if strings.Contains(err.Error(), fmt.Sprintf("download failed with status %d", s)) {
            return true
        }
    }
    return false
}

Try / catch

data, err := client.DownloadFile(ctx, fileURL)
if err != nil {
    if isDownloadStatusError(err, 403) {
        // signed URL expired — re-resolve and retry once
        if fresh, rErr := client.ResolveBlock(ctx, blockID); rErr == nil {
            return client.DownloadFile(ctx, fresh.File.File.URL)
        }
    }
    if isDownloadStatusError(err, 404) {
        return nil // attachment deleted; skip
    }
    return err // 5xx already retried with backoff inside DownloadFile
}

Prevention

When it happens

Trigger: The signed S3 URL has expired (Notion file URLs expire after ~1 hour) yielding 403; the attachment was deleted from the page yielding 404; the storage endpoint returns 500/503 beyond the retry budget; a rate-limited 429 from the storage host.

Common situations: Long-running syncs that hold block data for over an hour before downloading; retrying a job hours after fetching the URL list; attachments removed between indexing and download; transient storage outages exceeding maxRetries.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/7e51afbf8de1e887. Report an issue: GitHub.