{"id":"0784308e3dddd012","repo":"gohugoio/hugo","slug":"key-must-not-end-with-q","errorCode":null,"errorMessage":"key must not end with '/': %q","messagePattern":"key must not end with '/': %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hugolib/doctree/support.go","lineNumber":122,"sourceCode":"}\n\n// ValidateKey returns an error if the key is not valid.\nfunc ValidateKey(key string) error {\n\tif key == \"\" {\n\t\t// Root node.\n\t\treturn nil\n\t}\n\n\tif len(key) < 2 {\n\t\treturn fmt.Errorf(\"too short key: %q\", key)\n\t}\n\n\tif key[0] != '/' {\n\t\treturn fmt.Errorf(\"key must start with '/': %q\", key)\n\t}\n\n\tif key[len(key)-1] == '/' {\n\t\treturn fmt.Errorf(\"key must not end with '/': %q\", key)\n\t}\n\n\treturn nil\n}\n\n// Event is used to communicate events in the tree.\ntype Event[T any] struct {\n\tName            string\n\tPath            string\n\tSource          T\n\tstopPropagation bool\n}\n\ntype LockType int\n\n// MutableTree is a tree that can be modified.\ntype MutableTree interface {\n\tDeleteRaw(key string)","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/hugolib/doctree/support.go#L104-L140","documentation":"doctree.ValidateKey forbids trailing slashes on keys. Keys are canonical node identifiers (\"/blog/post\"); allowing \"/blog/post/\" would create two distinct keys for the same logical node and break prefix-based tree walks, so the trailing-slash form is rejected.","triggerScenarios":"Inserting or querying with a key ending in '/', e.g. \"/blog/\" — typically from joining a directory path with an empty segment, or passing a URL-style path (which conventionally keeps trailing slashes) directly as a tree key.","commonSituations":"Internal Hugo development or hugolib embedding: converting permalinks/URLs (which end in '/') into doctree keys without trimming; string concatenation like dir + \"/\" + name where name is empty.","solutions":["strings.TrimSuffix(key, \"/\") before use, or fix the join that appends the empty segment.","Don't use rendered URLs as tree keys — use the logical page path from Hugo's path helpers."],"exampleFix":"// before\ntree.Insert(dir+\"/\", n)\n// after\ntree.Insert(strings.TrimSuffix(dir, \"/\"), n)","handlingStrategy":"validation","validationCode":"key = strings.TrimSuffix(key, \"/\")\nif key == \"\" {\n    key = \"/\" // root is the only key allowed to be just '/'\n}","typeGuard":"func isValidTreeKey(key string) bool {\n    return key == \"/\" || (len(key) >= 2 && strings.HasPrefix(key, \"/\") && !strings.HasSuffix(key, \"/\"))\n}","tryCatchPattern":null,"preventionTips":["TrimSuffix trailing '/' when converting directory-style paths to tree keys","Remember URLs and section paths often carry trailing slashes — strip them at the boundary, not deep in the tree code","Run all externally sourced paths (config, front matter, taxonomy terms) through one normalization function","Cover trailing-slash inputs (\"/blog/\") explicitly in tests"],"tags":["hugo","doctree","validation","paths"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}