{"record":{"id":"19c8f5150a7fde7c","repo":"siyuan-note/siyuan","slug":"skill-resource-is-not-valid-utf-8-s-s","errorCode":null,"errorMessage":"skill resource is not valid UTF-8: %s/%s","messagePattern":"skill resource is not valid UTF-8: (.+?)/(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/util/skill.go","lineNumber":391,"sourceCode":"\t}\n\tif info.Size() > maxSkillResourceBytes {\n\t\treturn \"\", fmt.Errorf(\"skill resource exceeds the %d byte limit: %s/%s\", maxSkillResourceBytes, skillName, resource)\n\t}\n\n\tfile, err := os.Open(realTarget)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"skill resource not found: %s/%s\", skillName, resource)\n\t}\n\tdefer file.Close()\n\tdata, err := io.ReadAll(io.LimitReader(file, maxSkillResourceBytes+1))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"skill resource read failed: %s/%s\", skillName, resource)\n\t}\n\tif len(data) > maxSkillResourceBytes {\n\t\treturn \"\", fmt.Errorf(\"skill resource exceeds the %d byte limit: %s/%s\", maxSkillResourceBytes, skillName, resource)\n\t}\n\tif !utf8.Valid(data) {\n\t\treturn \"\", fmt.Errorf(\"skill resource is not valid UTF-8: %s/%s\", skillName, resource)\n\t}\n\treturn string(data), nil\n}\n\nfunc validateSkillName(name string) error {\n\tif name == \"\" || name == \".\" || name == \"..\" {\n\t\treturn fmt.Errorf(\"invalid skill name: %s\", name)\n\t}\n\tif strings.ContainsAny(name, `/\\`) {\n\t\treturn fmt.Errorf(\"invalid skill name: %s\", name)\n\t}\n\tdir := SkillsDir()\n\tabs := filepath.Join(dir, name)\n\tif !gulu.File.IsSubPath(dir, abs) {\n\t\treturn fmt.Errorf(\"invalid skill name: %s\", name)\n\t}\n\treturn nil\n}","sourceCodeStart":373,"sourceCodeEnd":409,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/util/skill.go#L373-L409","documentation":"Skill resources are returned to callers as UTF-8 text, so readSkillResource validates the raw bytes with utf8.Valid before returning. If the file contains binary data or non-UTF-8 text (Latin-1, GBK, UTF-16), the library rejects it rather than returning mojibake. Only textual resources encoded as UTF-8 are supported.","triggerScenarios":"LoadSkill -> readSkillResource: the resource file passes the size check but its bytes fail utf8.Valid — a binary file (image, zip, PDF), UTF-16 text with BOM, or legacy-encoded text (GBK/Shift-JIS/Latin-1) saved as a skill resource.","commonSituations":"Exporting files from Windows tools that default to ANSI/GBK encoding; storing images or archives inside a skill folder; editors saving with a non-UTF-8 charset; files downloaded in binary vs text mode.","solutions":["Convert the resource to UTF-8 encoding (iconv -f GBK -t UTF-8, or editor 'Save with encoding: UTF-8')","Replace UTF-16 files with UTF-8 versions (many Windows 'Unicode' saves are UTF-16)","Move binary assets (images, zip, PDF) out of the skill's text resources — they cannot be served as skill text","Check the file type with `file <path>` to confirm it is text before adding it as a resource"],"exampleFix":"# before\n$ file data.txt\ndata.txt: ISO-8859 text\n# after\n$ iconv -f ISO-8859-1 -t UTF-8 data.txt > data-utf8.txt\n$ mv data-utf8.txt data.txt","handlingStrategy":"validation","validationCode":"// Go: validate UTF-8 before saving a resource\nif !utf8.Valid(content) {\n    return fmt.Errorf(\"resource must be UTF-8 text\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Save all skill files with UTF-8 encoding; convert legacy encodings with iconv first","Never place binary assets (images, archives) in a skill's text resources","Run `file <path>` to confirm text encoding before adding a resource"],"tags":["go","encoding","utf-8","skills"],"backgroundTag":"invalid-argument-format","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}