siyuan-note/siyuan · error

344

344

Error message

Markdown [%s] is not valid UTF-8

What it means

A Markdown file's bytes are not valid UTF-8, so the importer refuses to process it (error code 344). SiYuan stores note content as UTF-8 text; importing non-UTF-8 bytes would corrupt block content, so the offending file is reported with its vault-relative path and the import of that document fails.

Solutions

  1. Re-encode the reported file to UTF-8 (iconv -f GBK -t UTF-8 file.md, or VS Code 'Save with Encoding → UTF-8')
  2. Detect the actual encoding with file/chardet before converting
  3. If the file is UTF-16 with BOM, convert to UTF-8 explicitly
  4. Exclude or delete the non-text file if it is not a real note

Example fix

$ iconv -f GB18030 -t UTF-8 notes/old.md > notes/old.utf8.md
$ mv notes/old.utf8.md notes/old.md
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
const buf = fs.readFileSync(mdFile);
const decoded = new TextDecoder('utf-8', { fatal: true }).decode(buf); // throws on invalid UTF-8

Prevention

When it happens

Trigger: readStableObsidianFile returns bytes for a `.md` source and utf8.Valid(data) is false — the file contains legacy encodings (GBK, Latin-1, UTF-16) or binary junk.

Common situations: Old notes created with Windows editors in a legacy codepage; files converted from Evernote/Word without re-encoding; UTF-16 files produced by PowerShell redirection; a `.md` file that is actually binary.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/e23dc87a05cc9819. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/import_obsidian.go:862

}

func analyzeObsidianDocuments(ctx context.Context, vault *obsidianVaultContext, progress func(int, string)) error {
	sourceCount := countObsidianSourceDocs(vault.Docs)
	processed := 0
	for _, doc := range vault.Docs {
		if doc.Synthetic {
			continue
		}
		if err := ctx.Err(); err != nil {
			return err
		}
		data, err := readStableObsidianFile(doc.Source)
		if err != nil {
			return newObsidianReadUserError(doc.Source, err)
		}
		if !utf8.Valid(data) {
			return newObsidianUserError(344, doc.Source.RelPath,
				fmt.Errorf("Markdown [%s] is not valid UTF-8", doc.Source.RelPath))
		}
		scan := scanObsidianSource(data)
		for _, blockID := range scan.BlockIDs {
			if scan.Duplicates[blockID] {
				doc.DuplicateBlocks[blockID] = true
			}
			if doc.BlockIDs[blockID] == "" {
				doc.BlockIDs[blockID] = ast.NewNodeID()
			}
		}
		tree, _, _, _ := parseStdMd(data)
		if tree == nil {
			return newObsidianUserError(347, doc.Source.RelPath,
				fmt.Errorf("parse Markdown [%s] failed", doc.Source.RelPath))
		}
		buildObsidianHeadingIndex(doc, tree)
		vault.Analysis.WikiLinkCount += countObsidianNonEmbedTokens(scan.Wikis)
		vault.Analysis.EmbedCount += countObsidianEmbedTokens(scan.Wikis)

View on GitHub (pinned to 9f775e8a12)