{"record":{"id":"ded177c5bae65161","repo":"usememos/memos","slug":"too-many-redirects","errorCode":null,"errorMessage":"too many redirects","messagePattern":"too many redirects","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/httpgetter/html_meta.go","lineNumber":44,"sourceCode":"\t\tKeepAlive: 30 * time.Second,\n\t}).DialContext\n\thttpClient = newHTTPClient()\n)\n\nfunc newHTTPClient() *http.Client {\n\ttransport := http.DefaultTransport.(*http.Transport).Clone()\n\ttransport.Proxy = nil\n\ttransport.DialContext = secureDialContext\n\n\treturn &http.Client{\n\t\tTransport: transport,\n\t\tTimeout:   5 * time.Second,\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\tif err := validateURL(req.URL.String()); err != nil {\n\t\t\t\treturn errors.Wrap(err, \"redirect to internal IP\")\n\t\t\t}\n\t\t\tif len(via) >= 10 {\n\t\t\t\treturn errors.New(\"too many redirects\")\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\nfunc secureDialContext(ctx context.Context, network, address string) (net.Conn, error) {\n\thost, port, err := net.SplitHostPort(address)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"invalid address\")\n\t}\n\n\tips, err := resolveAllowedIPs(ctx, host)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar dialErr error","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/httpgetter/html_meta.go#L26-L62","documentation":"Returned by the redirect policy of the hardened HTTP client in internal/httpgetter when a URL chain exceeds 10 redirects (len(via) >= 10). Each hop is also re-validated against the internal-IP check, so redirect loops through public hosts terminate here with this error.","triggerScenarios":"Fetching a URL that redirects more than 10 times: login walls, ad/tracker chains, misconfigured server redirect loops (A -> B -> A), or shorteners stacked more than 10 deep.","commonSituations":"Pasting a corporate SSO or newsletter link into a memo (redirect through several trackers); a server misconfigured with an http<->https or trailing-slash loop; stale short links that bounce between mirrors.","solutions":["Resolve the final URL first with curl -IL <url> and use the destination directly","If you own the server, fix the redirect loop (check https/http and trailing-slash canonicalization)","Retry manually following each hop to identify and skip the looping hop"],"exampleFix":"// before\nmeta, err := httpgetter.GetHTMLMeta(\"http://a.co/loop\")\n// after (use final destination)\nmeta, err := httpgetter.GetHTMLMeta(\"https://example.com/final-page\")","handlingStrategy":"try-catch","validationCode":"// Optionally pre-flight with a client that counts redirects (Go does not expose this without doing the request)\n// Instead, cap expectations client-side:\nfunc likelyRedirectLoop(u string) bool {\n  // heuristic: known shortener chains, or same host with alternating path\n  return strings.Count(u, \"://\") > 0 && len(u) > 200 // very long chained short URLs\n}","typeGuard":null,"tryCatchPattern":"// Treat as non-fatal: skip the preview when redirect budget is exceeded\nmeta, err := getter.GetHTMLMeta(u)\nif err != nil && strings.Contains(err.Error(), \"too many redirects\") {\n  meta = &httpgetter.HTMLMeta{Title: u} // degrade gracefully\n  err = nil\n}","preventionTips":["Store final destination URLs in notes rather than shortener chains","Skip link preview when fetching fails; do not block note creation on preview errors","If you own the redirecting server, cap chains and fix loops"],"tags":["network","http","redirect"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}