{"id":"70fa15d95c0be189","repo":"gin-gonic/gin","slug":"catch-all-routes-are-only-allowed-at-the-end-of-th","errorCode":null,"errorMessage":"catch-all routes are only allowed at the end of the path in path '${fullPath}'","messagePattern":"catch-all routes are only allowed at the end of the path in path '(.+?)'","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tree.go","lineNumber":345,"sourceCode":"\t\t\t\tpath = path[len(wildcard):]\n\n\t\t\t\tchild := &node{\n\t\t\t\t\tpriority: 1,\n\t\t\t\t\tfullPath: fullPath,\n\t\t\t\t}\n\t\t\t\tn.addChild(child)\n\t\t\t\tn = child\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// Otherwise we're done. Insert the handle in the new leaf\n\t\t\tn.handlers = handlers\n\t\t\treturn\n\t\t}\n\n\t\t// catchAll\n\t\tif i+len(wildcard) != len(path) {\n\t\t\tpanic(\"catch-all routes are only allowed at the end of the path in path '\" + fullPath + \"'\")\n\t\t}\n\n\t\tif len(n.path) > 0 && n.path[len(n.path)-1] == '/' {\n\t\t\tpathSeg := \"\"\n\t\t\tif len(n.children) != 0 {\n\t\t\t\tpathSeg, _, _ = strings.Cut(n.children[0].path, \"/\")\n\t\t\t}\n\t\t\tpanic(\"catch-all wildcard '\" + path +\n\t\t\t\t\"' in new path '\" + fullPath +\n\t\t\t\t\"' conflicts with existing path segment '\" + pathSeg +\n\t\t\t\t\"' in existing prefix '\" + n.path + pathSeg +\n\t\t\t\t\"'\")\n\t\t}\n\n\t\t// currently fixed width 1 for '/'\n\t\ti--\n\t\tif i < 0 || path[i] != '/' {\n\t\t\tpanic(\"no / before catch-all in path '\" + fullPath + \"'\")","sourceCodeStart":327,"sourceCodeEnd":363,"githubUrl":"https://github.com/gin-gonic/gin/blob/34dac209ffb6ef85cc78c5d217bbb7ad001d68fd/tree.go#L327-L363","documentation":"Thrown by (*node).insertChild in tree.go:345 when a catch-all wildcard ('*name') is not positioned at the very end of the path. A catch-all by definition consumes the rest of the URL, so anything after it (e.g. /files/*filepath/extra) is unreachable. The check is i+len(wildcard) != len(path), meaning the wildcard must extend to the final byte.","triggerScenarios":"Registering /download/*file/version, /api/*proxy/health, or /static/*css/style.css — any path where text follows the catch-all segment. Also triggered by trailing-slash confusion like /assets/*path/ (the slash after the wildcard counts as 'after').","commonSituations":"Designing a proxy or file-serving route and wanting to anchor something after the wildcard; refactoring a path and appending a suffix without realising the catch-all must come last.","solutions":["Move the catch-all to the end of the path: /download/:version/*file or /api/health/*proxy.","If you need to capture the entire tail including a logical suffix, parse it inside the handler from c.Param(\"file\") instead of expressing it in the route.","Drop the trailing slash after the wildcard if it is the cause: /assets/*path rather than /assets/*path/."],"exampleFix":"// before\nrouter.GET(\"/download/*file/version\", h) // panics\n\n// after — catch-all last\nrouter.GET(\"/download/:version/*file\", h)\n\n// or — parse the suffix in the handler\nrouter.GET(\"/download/*file\", func(c *gin.Context) {\n    parts := strings.SplitN(c.Param(\"file\"), \"/version\", 2)\n    // parts[0] = file path, parts[1] (if present) = remainder\n})","handlingStrategy":"validation","validationCode":"// Reject catch-all wildcards that are not at the end of the path.\nfunc validateCatchAllAtEnd(p string) error {\n    idx := strings.Index(p, \"*\")\n    if idx == -1 {\n        return nil\n    }\n    if idx != len(p)-1 && !strings.HasSuffix(p, \"/\") {\n        // find end of the wildcard segment\n        end := strings.IndexByte(p[idx:], '/')\n        if end != -1 {\n            return fmt.Errorf(\"catch-all in %q must be the last segment\", p)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"func safeRegister(e *gin.Engine, method, path string, h gin.HandlerFunc) (err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"catch-all position invalid in %s: %v\", path, r)\n        }\n    }()\n    e.Handle(method, path, h)\n    return nil\n}","preventionTips":["Treat a '*' wildcard as the terminal segment of the route — nothing may follow it, not even a slash.","Reorder path components so the catch-all is last.","Move anything after the wildcard into the handler and parse it from c.Param."],"tags":["routing","invalid-path","catch-all","gin","startup-panic"],"analyzedSha":"34dac209ffb6ef85cc78c5d217bbb7ad001d68fd","analyzedAt":"2026-08-04T21:26:18.438Z","schemaVersion":2}