{"record":{"id":"cc30e65d7b7b65f2","repo":"siyuan-note/siyuan","slug":"code-block-has-no-code-node","errorCode":null,"errorMessage":"code block has no code node","messagePattern":"code block has no code node","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/export.go","lineNumber":117,"sourceCode":"\n\terr = withExportReadLockByBlockID(blockID, func() error {\n\t\ttree, _ := LoadTreeByBlockID(blockID)\n\t\tif nil == tree {\n\t\t\treturn ErrBlockNotFound\n\t\t}\n\n\t\tnode := treenode.GetNodeInTree(tree, blockID)\n\t\tif nil == node {\n\t\t\treturn ErrBlockNotFound\n\t\t}\n\n\t\tif ast.NodeCodeBlock != node.Type {\n\t\t\treturn errors.New(\"not a code block\")\n\t\t}\n\n\t\tcode := node.ChildByType(ast.NodeCodeBlockCode)\n\t\tif nil == code {\n\t\t\treturn errors.New(\"code block has no code node\")\n\t\t}\n\n\t\tname := tree.Root.IALAttr(\"title\") + \"-\" + util.CurrentTimeSecondsStr() + \".txt\"\n\t\tname = util.FilterFileName(name)\n\t\texportFolder := filepath.Join(util.TempDir, \"export\")\n\t\t// 加密笔记本的导出归入 boxID 子目录，确保 LockBox 清理和服务端校验锁定状态\n\t\tif IsEncryptedBox(tree.Box) {\n\t\t\texportFolder = filepath.Join(exportFolder, tree.Box)\n\t\t}\n\t\texportFolder = filepath.Join(exportFolder, \"code\")\n\t\tif mkdirErr := os.MkdirAll(exportFolder, 0755); mkdirErr != nil {\n\t\t\treturn mkdirErr\n\t\t}\n\n\t\tcode.Tokens = bytes.ReplaceAll(code.Tokens, []byte(editor.Zwj+\"```\"), []byte(\"```\"))\n\n\t\twritePath := filepath.Join(exportFolder, name)\n\t\tif writeErr := filelock.WriteFile(writePath, code.Tokens); writeErr != nil {","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/export.go#L99-L135","documentation":"ExportCodeBlock found a node of type ast.NodeCodeBlock but its ChildByType(ast.NodeCodeBlockCode) returned nil, meaning the code block AST is missing the child node that holds the actual code text. This indicates a malformed or unexpectedly structured code block in the tree — an internal invariant violation rather than normal caller error.","triggerScenarios":"Calling ExportCodeBlock on a code block whose serialized .sy tree lost its NodeCodeBlockCode child — e.g. corrupted/partially written .sy file, a tree produced by an older/abnormal format, or a node constructed by a plugin without the code child.","commonSituations":"Data corruption or interrupted writes to the .sy file; importing or generating documents programmatically with hand-built ASTs; version changes in the lute AST format.","solutions":["Rebuild the tree/index for the document (re-parse the .sy via Lute) and retry.","Open the block in the editor, re-type or re-create the code block, then export again.","Restore the document from history/snapshot if the .sy file is corrupted.","If reproducible from generated input, fix the producer so it emits a NodeCodeBlockCode child."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"node := treenode.GetNodeInTree(tree, blockID)\nif node != nil && node.Type == ast.NodeCodeBlock && node.ChildByType(ast.NodeCodeBlockCode) == nil {\n    return errors.New(\"malformed code block; rebuild the document index\")\n}","typeGuard":"func hasCodeChild(node *ast.Node) bool { return node != nil && node.ChildByType(ast.NodeCodeBlockCode) != nil }","tryCatchPattern":"if err != nil {\n    if err.Error() == \"code block has no code node\" {\n        // trigger doc re-index or restore from history\n    }\n    return err\n}","preventionTips":["Avoid hand-building AST nodes in plugins without the NodeCodeBlockCode child","Restore from snapshots if .sy files show corruption signs","Keep kernel and lute versions in sync to avoid AST format drift"],"tags":["ast","corruption","export"],"backgroundTag":"internal-invariant-violation","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}