GopeedLab/gopeed · error
redirect failed
Error message
redirect failed
What it means
The injected XMLHttpRequest/fetch client (pkg/download/engine/inject/xhr/module.go:238-245) installs a redirect policy per request. When the script sets redirect: "error" (the fetch RequestInit 'error' mode), any 3xx response from the server makes the Go http client abort with this error — by design, redirects are treated as failures instead of being followed.
Source
Thrown at pkg/download/engine/inject/xhr/module.go:244
}
// Only string body can specify Content-Type header by user
if contentType != "" && (!isStringBody || xhr.requestHeaders.Get("Content-Type") == "") {
reqBuilder.SetHeader("Content-Type", contentType)
}
// Set timeout
if xhr.Timeout > 0 {
xhr.client.SetTimeout(time.Duration(xhr.Timeout) * time.Millisecond)
}
// Configure redirect behavior
xhr.client.SetRedirectPolicy(func(req *http.Request, via []*http.Request) error {
if xhr.Redirect == redirectManual {
return http.ErrUseLastResponse
}
if xhr.Redirect == redirectError {
return errors.New("redirect failed")
}
if len(via) > 20 {
return errors.New("too many redirects")
}
return nil
})
// Execute request
resp, err := reqBuilder.Send(xhr.method, xhr.url)
if err != nil {
// handle timeout error
var ne net.Error
if errors.As(err, &ne) && ne.Timeout() {
if xhr.Timeout > 0 {
xhr.Upload.callOntimeout()
xhr.callOntimeout()
}
returnView on GitHub (pinned to 7b7327ffb3)
Solutions
- Use redirect: 'follow' (default) when the final content is what matters
- Keep 'error' only if you deliberately detect redirects — catch the error and re-issue manually against resp.headers.location (or with 'manual' mode to inspect the 3xx response)
- If the redirect is environmental (http->https), request the final URL directly
- Treat this error as a signal, not a failure, when using 'error' mode deliberately
Example fix
// before (script)
const r = await fetch(url, { redirect: 'error' }) // throws when server 302s
// after (script)
const r = await fetch(url, { redirect: 'follow' })
// or detect explicitly:
const m = await fetch(url, { redirect: 'manual' })
if (m.status >= 300 && m.status < 400) { /* read m.headers.get('location') */ } Defensive patterns
Strategy: fallback
Validate before calling
// Script-side: only use redirect:'error' when you handle the failure:
if (needFinalUrlOnly) { opts.redirect = 'follow' }
const r = await fetch(url, opts) Try / catch
try {
const r = await fetch(url, { redirect: 'error' })
} catch (e) {
// fallback: retry following redirects
const r2 = await fetch(url, { redirect: 'follow' })
} Prevention
- Default to redirect:'follow'; use 'error'/'manual' only with explicit handling
- Use 'manual' to inspect the 3xx response (status/Location) instead of 'error' when detecting redirects
- Pre-resolve known redirecting URLs (shorteners, http->https) to their final form
When it happens
Trigger: A script creates a request with {redirect: 'error'} (or an XHR configured to that mode) and the server answers 301/302/303/307/308; resolving a URL shortener or CDN that always redirects; a site that redirects http->https or adds a trailing slash, hit by a script that forbids redirects.
Common situations: Scripts copied from browser/fetch code that set redirect:'error' to detect redirects, without a handler for that case; pre-flight expectations that the URL is final; mixed-content or canonicalization redirects encountered only in production.
Related errors
- too many redirects
- redirect failed
- too many redirects
- Network request timed out
- Network request failed: %w
AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16).
Data as JSON: /api/errors/3a8fc9031d17e302.
Report an issue: GitHub.