{"record":{"id":"e28686b0f03c29c2","repo":"gohugoio/hugo","slug":"handle-cannot-replace-nodes-in-the-tree-use-trans","errorCode":null,"errorMessage":"Handle cannot replace nodes in the tree, use Transform for that","messagePattern":"Handle cannot replace nodes in the tree, use Transform for that","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hugolib/doctree/nodeshifttree.go","lineNumber":596,"sourceCode":"\t\t\t\t\t\tns = NodeTransformStateSkip\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}()\n\n\t\t\t\tif ns == NodeTransformStateTerminate || err != nil {\n\t\t\t\t\treturn radix.WalkStop, zero, err\n\t\t\t\t}\n\n\t\t\t\tif ns == NodeTransformStateSkip {\n\t\t\t\t\treturn radix.WalkContinue, zero, nil\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\tif r.Handle != nil {\n\t\t\t\tf, err := r.Handle(s, t)\n\t\t\t\tif f.ShouldSet() {\n\t\t\t\t\tpanic(\"Handle cannot replace nodes in the tree, use Transform for that\")\n\t\t\t\t}\n\t\t\t\tif f.ShouldStop() || err != nil {\n\t\t\t\t\treturn f, zero, err\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ns == NodeTransformStateTerminate {\n\t\t\t\treturn radix.WalkStop, zero, nil\n\t\t\t}\n\t\t\tif ns == NodeTransformStateReplaced {\n\t\t\t\treturn radix.WalkSet | radix.WalkContinue, t2, nil\n\t\t\t}\n\t\t\treturn radix.WalkContinue, zero, nil\n\t\t}\n\n\t\thandle := &walkHandler[T]{\n\t\t\tr:      r,\n\t\t\thandle: handleV,\n\t\t}","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/hugolib/doctree/nodeshifttree.go#L578-L614","documentation":"A panic in NodeShiftTreeWalker.Walk (hugolib/doctree/nodeshifttree.go:596) when the Handle callback returns a WalkFlag with the Set bit (radix.WalkSet). Replacing nodes is the exclusive job of Transform; Handle is read-only with respect to node replacement.","triggerScenarios":"A Handle callback returns radix.WalkSet | radix.WalkContinue, attempting to replace the current node.","commonSituations":"Copying a WalkFn that returns WalkSet into Handle instead of Transform; misunderstanding the Handle/Transform split.","solutions":["Move node-replacement logic into Transform and set NoShift=true","Have Handle return only WalkContinue/WalkStop without WalkSet"],"exampleFix":"// before\nHandle: func(s string, v contentNode) (radix.WalkFlag, error) {\n    return radix.WalkSet | radix.WalkContinue, nil\n}\n// after\nHandle: func(s string, v contentNode) (radix.WalkFlag, error) {\n    return radix.WalkContinue, nil\n}","handlingStrategy":"validation","validationCode":"// Inside a Handle callback, never return WalkSet.\nreturn radix.WalkContinue, nil","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep Handle callbacks read-only (WalkContinue/WalkStop only)","Use Transform (with NoShift=true) when node replacement is needed","Audit WalkFn return values copied between Transform and Handle"],"tags":["doctree","walker","api-misuse","panic"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}