{"id":"177b63afe3820c0f","repo":"gin-gonic/gin","slug":"no-before-catch-all-in-path-fullpath","errorCode":null,"errorMessage":"no / before catch-all in path '${fullPath}'","messagePattern":"no / before catch-all in path '(.+?)'","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tree.go","lineNumber":363,"sourceCode":"\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 + \"'\")\n\t\t}\n\n\t\tn.path = path[:i]\n\n\t\t// First node: catchAll node with empty path\n\t\tchild := &node{\n\t\t\twildChild: true,\n\t\t\tnType:     catchAll,\n\t\t\tfullPath:  fullPath,\n\t\t}\n\n\t\tn.addChild(child)\n\t\tn.indices = \"/\"\n\t\tn = child\n\t\tn.priority++\n\n\t\t// second node: node holding the variable\n\t\tchild = &node{","sourceCodeStart":345,"sourceCodeEnd":381,"githubUrl":"https://github.com/gin-gonic/gin/blob/34dac209ffb6ef85cc78c5d217bbb7ad001d68fd/tree.go#L345-L381","documentation":"Thrown by (*node).insertChild in tree.go:363 during catch-all insertion when the byte immediately before the '*' marker is not '/'. Gin models a catch-all as a two-node structure: a parent node ending in '/' and a child holding '/*name'. If the path lacks the slash (e.g. /files*name or /foo*bar) the parent cannot be split and the tree rejects the route.","triggerScenarios":"Registering /files*filepath, /download*archive, or any catch-all written without the separating slash. Also triggered by path manipulation that strips the slash, e.g. strings.TrimSuffix(basePath, \"/\") + \"*all\".","commonSituations":"Typing a catch-all quickly and omitting the slash; building paths programmatically and forgetting the delimiter; copy-pasting a param-style segment (':') and converting only the marker to '*' without adding the slash.","solutions":["Insert a '/' before the '*': /files/*filepath instead of /files*filepath.","When concatenating a base path and a catch-all, ensure the join introduces exactly one slash: path.Join(base, \"/*filepath\") or assert the boundary explicitly.","Lint routes with a regex that forbids a non-'/' byte directly preceding '*'."],"exampleFix":"// before\nrouter.GET(\"/files*filepath\", h)   // panics: no '/' before '*'\n\n// after\nrouter.GET(\"/files/*filepath\", h)","handlingStrategy":"validation","validationCode":"// Reject catch-all markers not preceded by '/'.\nvar badCatchAll = regexp.MustCompile(`[^/]\\*`)\n\nfunc validateCatchAllSlash(p string) error {\n    if badCatchAll.MatchString(p) {\n        return fmt.Errorf(\"path %q: '*' must be preceded by '/'\", p)\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(\"missing slash before catch-all in %s: %v\", path, r)\n        }\n    }()\n    e.Handle(method, path, h)\n    return nil\n}","preventionTips":["Always write catch-alls as '/*name', never '*name' glued to the previous segment.","When joining a base path with a catch-all, use path.Join and verify the slash boundary.","Lint every route for the regex /[^/]\\*/."],"tags":["routing","invalid-path","catch-all","gin","startup-panic"],"analyzedSha":"34dac209ffb6ef85cc78c5d217bbb7ad001d68fd","analyzedAt":"2026-08-04T21:26:18.438Z","schemaVersion":2}