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()
			}
			return

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Use redirect: 'follow' (default) when the final content is what matters
  2. 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)
  3. If the redirect is environmental (http->https), request the final URL directly
  4. 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

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


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/3a8fc9031d17e302. Report an issue: GitHub.