{"record":{"id":"3a8fc9031d17e302","repo":"GopeedLab/gopeed","slug":"redirect-failed","errorCode":null,"errorMessage":"redirect failed","messagePattern":"redirect failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/download/engine/inject/xhr/module.go","lineNumber":244,"sourceCode":"\t}\n\n\t// Only string body can specify Content-Type header by user\n\tif contentType != \"\" && (!isStringBody || xhr.requestHeaders.Get(\"Content-Type\") == \"\") {\n\t\treqBuilder.SetHeader(\"Content-Type\", contentType)\n\t}\n\n\t// Set timeout\n\tif xhr.Timeout > 0 {\n\t\txhr.client.SetTimeout(time.Duration(xhr.Timeout) * time.Millisecond)\n\t}\n\n\t// Configure redirect behavior\n\txhr.client.SetRedirectPolicy(func(req *http.Request, via []*http.Request) error {\n\t\tif xhr.Redirect == redirectManual {\n\t\t\treturn http.ErrUseLastResponse\n\t\t}\n\t\tif xhr.Redirect == redirectError {\n\t\t\treturn errors.New(\"redirect failed\")\n\t\t}\n\t\tif len(via) > 20 {\n\t\t\treturn errors.New(\"too many redirects\")\n\t\t}\n\t\treturn nil\n\t})\n\n\t// Execute request\n\tresp, err := reqBuilder.Send(xhr.method, xhr.url)\n\tif err != nil {\n\t\t// handle timeout error\n\t\tvar ne net.Error\n\t\tif errors.As(err, &ne) && ne.Timeout() {\n\t\t\tif xhr.Timeout > 0 {\n\t\t\t\txhr.Upload.callOntimeout()\n\t\t\t\txhr.callOntimeout()\n\t\t\t}\n\t\t\treturn","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/download/engine/inject/xhr/module.go#L226-L262","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (script)\nconst r = await fetch(url, { redirect: 'error' }) // throws when server 302s\n\n// after (script)\nconst r = await fetch(url, { redirect: 'follow' })\n// or detect explicitly:\nconst m = await fetch(url, { redirect: 'manual' })\nif (m.status >= 300 && m.status < 400) { /* read m.headers.get('location') */ }","handlingStrategy":"fallback","validationCode":"// Script-side: only use redirect:'error' when you handle the failure:\nif (needFinalUrlOnly) { opts.redirect = 'follow' }\nconst r = await fetch(url, opts)","typeGuard":null,"tryCatchPattern":"try {\n    const r = await fetch(url, { redirect: 'error' })\n} catch (e) {\n    // fallback: retry following redirects\n    const r2 = await fetch(url, { redirect: 'follow' })\n}","preventionTips":["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"],"tags":["network","http","redirect","javascript"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}