anomalyco/sst · error
failed to upload assets: HTTP %d %s
Error message
failed to upload assets: HTTP %d %s
What it means
Cloudflare's Workers Assets upload API responds 201 Created when all buckets are uploaded and 202 Accepted when more buckets remain. Any other status is treated as a failed upload and this error includes the HTTP status code plus the response body for diagnosis. It indicates Cloudflare rejected the request — usually auth, account, or payload problems.
Source
Thrown at pkg/server/resource/cloudflare-worker-assets.go:283
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "multipart/form-data; boundary="+writer.Boundary())
req.Header.Set("Authorization", "Bearer "+jwt)
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
// API returns:
// - 202 Accepted if there are more buckets to upload
// - 201 Created if all buckets have been uploaded
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted {
responseBody, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("failed to upload assets: HTTP %d %s", resp.StatusCode, string(responseBody))
}
// Decode response
var result struct {
Result UploadResponse `json:"result"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", err
}
return result.Result.Jwt, nil
}View on GitHub (pinned to a0bd20f762)
Solutions
- Read the responseBody in the error — Cloudflare includes an errors[] array with codes/messages
- Verify the API token is valid and has Workers Scripts/Assets edit permission for the account
- Confirm accountId matches the account the worker/assets upload session was created in
- Check asset hashes match what the completion session expects (recompute and re-upload changed files)
- For 202 responses ensure the caller continues uploading remaining buckets until 201
- Retry after a delay on 429/5xx, respecting Cloudflare rate limits
Example fix
// before
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted {
responseBody, _ := io.ReadAll(resp.Body)
return "", fmt.Errorf("failed to upload assets: HTTP %d %s", resp.StatusCode, string(responseBody))
}
// after
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusAccepted {
responseBody, _ := io.ReadAll(resp.Body)
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
return "", fmt.Errorf("cloudflare auth rejected asset upload (HTTP %d): check API token and permissions: %s", resp.StatusCode, string(responseBody))
}
return "", fmt.Errorf("failed to upload assets: HTTP %d %s", resp.StatusCode, string(responseBody))
} Defensive patterns
Strategy: retry
Validate before calling
// before upload: verify token and account
req, _ := http.NewRequest("GET", "https://api.cloudflare.com/client/v4/accounts/"+accountId, nil)
req.Header.Set("Authorization", "Bearer "+apiToken)
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != 200 {
return fmt.Errorf("cloudflare token/account invalid before asset upload (HTTP %v)", resp.StatusCode)
} Type guard
func isUploadAccepted(status int) bool {
return status == http.StatusCreated || status == http.StatusAccepted
} Try / catch
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("asset upload request failed: %w", err)
}
if !isUploadAccepted(resp.StatusCode) {
responseBody, _ := io.ReadAll(resp.Body)
if resp.StatusCode == 429 || resp.StatusCode >= 500 {
// retry with backoff
}
return "", fmt.Errorf("failed to upload assets: HTTP %d %s", resp.StatusCode, string(responseBody))
} Prevention
- Keep the Cloudflare API token valid with Workers edit permissions
- Confirm the accountId matches the token's account
- Handle 202 by continuing to upload remaining buckets until 201
- Retry 429/5xx with exponential backoff
- Always log the response body — Cloudflare error codes pinpoint the cause
When it happens
Trigger: POST to /client/v4/accounts/{accountId}/workers/assets/upload?base64=true returns a status other than 201 or 202 (pkg/server/resource/cloudflare-worker-assets.go:283). Typical statuses: 401/403 (bad or missing API token, token lacking Workers Scripts Edit), 404 (wrong accountId or assets upload session not initialized), 400/413 (malformed multipart body, hash mismatch, or payload too large per-bucket limits).
Common situations: Expired or revoked Cloudflare API token; token missing the Workers Assets permission; CLOUDFLARE_ACCOUNT_ID mismatch with the token's account; uploading more assets than fit in one bucket without following the 202 continuation; changed upload-session metadata causing hash mismatch.
Related errors
- failed to complete asset upload - no completion JWT received
- failed to create form part %s: %w
- failed to close writer: %w
- failed to create DNS record, status: %d, response: %s
- bucket %v upload failed: %w
AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30).
Data as JSON: /api/errors/da53a34f03742a1e.
Report an issue: GitHub.