{"record":{"id":"d745700067edd7f1","repo":"siyuan-note/siyuan","slug":"invalid-template-source","errorCode":null,"errorMessage":"invalid template source","messagePattern":"invalid template source","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/template_manage.go","lineNumber":171,"sourceCode":"\t\treturn \"\", err\n\t}\n\tif !info.Mode().IsRegular() || info.Size() > maxTemplateSourceSize {\n\t\treturn \"\", errors.New(\"invalid template source file\")\n\t}\n\tcontent, err := root.ReadFile(p)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !utf8.Valid(content) {\n\t\treturn \"\", errors.New(\"template source is not UTF-8\")\n\t}\n\treturn string(content), nil\n}\n\n// 同目录临时文件写入完成后替换，写入失败时保留原模板。\nfunc writeTemplateSource(root *os.Root, p, content string, create bool) error {\n\tif !utf8.ValidString(content) {\n\t\treturn errors.New(\"invalid template source\")\n\t}\n\tif create {\n\t\tfile, err := root.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = file.WriteString(content)\n\t\tif err == nil {\n\t\t\terr = file.Sync()\n\t\t}\n\t\tcloseErr := file.Close()\n\t\tif err != nil {\n\t\t\troot.Remove(p)\n\t\t\treturn err\n\t\t}\n\t\treturn closeErr\n\t}\n\ttmp := path.Join(path.Dir(p), \".template-\"+ast.NewNodeID())","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/template_manage.go#L153-L189","documentation":"writeTemplateSource is the single write path for template content, used by both create and replace. It first verifies the incoming content string is valid UTF-8; since Go strings can technically hold invalid UTF-8 bytes (e.g. from raw byte slices or decoded JSON with escapes), this rejects content that could not be stored as a proper text template. The write is then performed atomically via a same-directory temp file and rename.","triggerScenarios":"ManageTemplateFiles with action=\"write\" where request.Content, after JSON decoding, contains invalid UTF-8 — typically when a client sends raw binary bytes or a string built from non-UTF-8 byte data instead of properly encoded text.","commonSituations":"A script POSTs file bytes read with the wrong encoding; a client library mangles multi-byte characters; an integration writes templates from a legacy-encoded source without converting first.","solutions":["Ensure the HTTP client sends the content as a UTF-8 JSON string (string escapes like \\uXXXX are fine)","Convert the source text to UTF-8 before writing, e.g. iconv or an encoding-aware reader in your script","If writing from bytes in Go/Node, validate utf8.Valid(bytes) / Buffer.isUtf8(bytes) before embedding into the JSON payload"],"exampleFix":"// before (Go client)\ncontent := string(rawGBKBytes)\nreq := TemplateFileRequest{Action: \"write\", Path: \"t.md\", Content: content}\n// after\nif !utf8.Valid(rawGBKBytes) { rawGBKBytes, _ = gbkToUTF8(rawGBKBytes) }\nreq := TemplateFileRequest{Action: \"write\", Path: \"t.md\", Content: string(rawGBKBytes)}","handlingStrategy":"validation","validationCode":"const bytes = Buffer.from(content, 'utf8');\nif (!Buffer.isUtf8(bytes)) {\n  throw new Error('content is not valid UTF-8');\n}","typeGuard":null,"tryCatchPattern":"try {\n  await manageTemplateFiles({ action: 'write', path: p, revision: rev, content });\n} catch (e) {\n  if (String(e.message).includes('invalid template source')) {\n    // re-encode content from its source encoding to UTF-8 and retry\n  }\n}","preventionTips":["Send template content as JSON strings over HTTP (never raw bytes in a text field)","Convert legacy encodings (GBK, Shift-JIS) to UTF-8 before writing","In Go clients, check utf8.ValidString before building the request"],"tags":["encoding","utf-8","template","write","validation"],"backgroundTag":"invalid-encoding","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}