{"record":{"id":"9dfa2996809a7335","repo":"kataras/iris","slug":"redirect-match-loop-detected-pattern-s-vs-targ","errorCode":null,"errorMessage":"redirect match: loop detected: pattern: %s vs target: %s","messagePattern":"redirect match: loop detected: pattern: (.+?) vs target: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"middleware/rewrite/rewrite.go","lineNumber":304,"sourceCode":"\n\tcodeStr, pattern, target := parts[0], parts[1], parts[2]\n\n\tfor i, ch := range codeStr {\n\t\tif !isDigit(ch) {\n\t\t\treturn nil, fmt.Errorf(\"redirect match: status code digits: %s [%d:%c]\", codeStr, i, ch)\n\t\t}\n\t}\n\n\tcode, err := strconv.Atoi(codeStr)\n\tif err != nil {\n\t\t// this should not happen, we check abt digit\n\t\t// and correctly position the error too but handle it.\n\t\treturn nil, fmt.Errorf(\"redirect match: status code digits: %s: %v\", codeStr, err)\n\t}\n\n\tregex := regexp.MustCompile(pattern)\n\tif regex.MatchString(target) {\n\t\treturn nil, fmt.Errorf(\"redirect match: loop detected: pattern: %s vs target: %s\", pattern, target)\n\t}\n\n\tv := &redirectMatch{\n\t\tcode:              code,\n\t\tpattern:           regex,\n\t\ttarget:            target,\n\t\tnoRedirect:        code <= 0,\n\t\tisRelativePattern: pattern[0] == '/', // search by path.\n\t}\n\n\treturn v, nil\n}\n\nfunc isDigit(ch rune) bool {\n\treturn '0' <= ch && ch <= '9'\n}\n\nfunc getPort(hostport string) string { // returns :port, note that this is only called on non-loopbacks.","sourceCodeStart":286,"sourceCodeEnd":322,"githubUrl":"https://github.com/kataras/iris/blob/7bedaf55a0b64bbb2248a5845a2c60d81a30996a/middleware/rewrite/rewrite.go#L286-L322","documentation":"The rewrite parser rejects redirect rules whose regex pattern also matches its own target, because that creates an infinite redirect loop: once the client is sent to the target, the rule would match it again. The check compiles the pattern and tests it against the target string, failing with this error when they overlap.","triggerScenarios":"Calling rewrite.New with a line like '301 /(.*) /$1' or any pattern that matches the replacement target, e.g. pattern '/blog' with target '/blog/new'.","commonSituations":"Broad catch-all redirect patterns (match everything) combined with targets that retain the matched path; migrating a site where old and new URLs share a prefix.","solutions":["Make the pattern more specific so it cannot match the target, e.g. anchor it: '^/old/(.*)$'.","Change the target to a path outside the pattern's domain (different prefix or TLD).","Use a negative-construct or narrower regex that excludes the destination path.","Split broad rules into several narrow rules whose targets do not self-match."],"exampleFix":"// before\n\"301 /(.*) /new/$1\" // pattern matches /new/... target -> loop\n// after\n\"301 ^/old/(.*)$ /new/$1\"","handlingStrategy":"validation","validationCode":"re := regexp.MustCompile(pattern)\nif re.MatchString(target) {\n    return fmt.Errorf(\"redirect rule %q would loop: target %q matches pattern\", pattern, target)\n}","typeGuard":null,"tryCatchPattern":"if err := rewrite.New(lines); err != nil {\n    if strings.Contains(err.Error(), \"loop detected\") {\n        log.Fatalf(\"fix redirect pattern %v\", err)\n    }\n}","preventionTips":["Anchor redirect patterns (^...$) so they can't match their own targets.","Send redirects to a different path prefix than the one matched.","Test each rule with the pattern.MatchString(target) check before deploying."],"tags":["rewrite","redirect","infinite-loop","config"],"backgroundTag":"redirect-loop","analyzedSha":"7bedaf55a0b64bbb2248a5845a2c60d81a30996a","analyzedAt":"2026-08-30T20:38:16.250Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}