{"record":{"id":"568320ebd72f8660","repo":"siyuan-note/siyuan","slug":"not-a-code-block","errorCode":null,"errorMessage":"not a code block","messagePattern":"not a code block","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/export.go","lineNumber":112,"sourceCode":"\treturn\n}\n\nfunc ExportCodeBlock(blockID string) (filePath string, err error) {\n\t// Supports exporting a code block as a file https://github.com/siyuan-note/siyuan/pull/16774\n\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}","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/export.go#L94-L130","documentation":"ExportCodeBlock exports a code block's content as a .txt file. Before exporting it checks that the node resolved from the tree is actually of type ast.NodeCodeBlock; if the blockID points to any other node type (paragraph, heading, table, etc.) it returns errors.New(\"not a code block\"). The API contract simply does not apply to non-code blocks.","triggerScenarios":"Calling ExportCodeBlock(blockID) with a blockID that exists in the tree but whose node.Type != ast.NodeCodeBlock — e.g. passing a document ID, heading ID, or paragraph ID.","commonSituations":"Frontend/plugin code assumes the focused block is a code block; stale IDs after the block was converted to another type; batch export scripts iterating block IDs without filtering by type.","solutions":["Check the block type before calling: load the tree node and confirm it is a code block.","In the UI, only invoke the code-block export action from a code block's menu context.","For batch exports, filter IDs by type via SQL (type = 'NodeCodeBlock') before calling the API."],"exampleFix":"// before\nExportCodeBlock(anyBlockID)\n// after\nif node := treenode.GetNodeInTree(tree, blockID); node != nil && node.Type == ast.NodeCodeBlock {\n    ExportCodeBlock(blockID)\n}","handlingStrategy":"validation","validationCode":"node := treenode.GetNodeInTree(tree, blockID)\nif node == nil || node.Type != ast.NodeCodeBlock {\n    return errors.New(\"block is not a code block\")\n}\nfilePath, err := model.ExportCodeBlock(blockID)","typeGuard":"func isCodeBlock(node *ast.Node) bool { return node != nil && node.Type == ast.NodeCodeBlock }","tryCatchPattern":"if err != nil {\n    if err.Error() == \"not a code block\" {\n        // fall back to another export path for non-code blocks\n    }\n    return err\n}","preventionTips":["Filter candidate block IDs by SQL type 'NodeCodeBlock' before batch export","Only wire the export action into code-block context menus","Re-validate block type after any block conversion operation"],"tags":["export","block-type","validation"],"backgroundTag":"incompatible-source-type","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"}