siyuan-note/siyuan · error

Conf.Language(388) with escaped relative path…

Error message

Conf.Language(388) with escaped relative path (htmlstd.EscapeString(finalRelPath))

What it means

During .sy import, each document tree is written into the target notebook via writeImportedTree. If writing fails specifically because the target notebook is locked/encrypted and currently not unlocked (errImportedTreeBoxLocked), the error is replaced with the localized message for language key 388, which embeds the document's relative path — HTML-escaped first since that path comes from the imported package. Any other write error is propagated unchanged to avoid mis-attribution.

Solutions

  1. Unlock the target encrypted notebook (enter its passphrase via the unlock dialog/API) to obtain a decryption lease, then retry the import.
  2. If the notebook should not be encrypted, decrypt/remove encryption on the target notebook before importing.
  3. If the write error is not lock-related, inspect the original error logged by logging.LogErrorf (the kernel log) for the real cause (disk full, permissions).
  4. Import into a different, unencrypted notebook or create a new notebook (createNotebook=true) instead.

Example fix

// before
fetch('/api/import/sy', {body: JSON.stringify({notebook: boxID, src: 'pkg.zip'})}) // notebook locked
// after
await unlockNotebook(boxID, passphrase) // obtain lease first
fetch('/api/import/sy', {body: JSON.stringify({notebook: boxID, src: 'pkg.zip'})})
Defensive patterns

Strategy: try-catch

Validate before calling

const box = await getNotebook(boxID);
if (box.closed) { await openNotebook(boxID); }
// if the notebook is encrypted, ensure an unlock lease exists before importing

Try / catch

try {
  await importSY(boxID, src);
} catch (e) {
  if (String(e.msg).includes('388') || /解锁|unlock/i.test(e.msg)) {
    await unlockNotebook(boxID, passphrase);
    return importSY(boxID, src); // retry once after unlocking
  }
  throw e;
}

Prevention

When it happens

Trigger: Importing a .sy/.sy.zip package into an encrypted notebook whose lease is not unlocked: writeImportedTree rejects the write with errImportedTreeBoxLocked, and importSY0 converts it to the unlock prompt containing the escaped relative path of the document being written.

Common situations: 1) Attempting an import into an encrypted notebook while its unlock session has expired. 2) Restarting the kernel between unlocking and importing. 3) Automating imports against an encrypted notebook without an unlock call first.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/import.go:951

		renderer := render.NewJSONRenderer(tree, luteEngine.RenderOptions, luteEngine.ParseOptions)
		data := renderer.Render()

		if !util.UseSingleLineSave {
			buf := bytes.Buffer{}
			buf.Grow(1024 * 1024 * 2)
			if err = json.Indent(&buf, data, "", "\t"); err != nil {
				return
			}
			data = buf.Bytes()
		}

		newSyPath := filepath.Join(filepath.Dir(syPath), finalSyName)
		if err = writeImportedTree(boxID, syPath, newSyPath, finalRelPath, data); err != nil {
			logging.LogErrorf("write imported .sy [%s] failed: %s", syPath, err)
			// 只有"目标笔记本未解锁导致拒绝写盘"才替换为提示解锁的文案,其余写盘错误原样上抛,避免归因错误。
			// 相对路径的父目录名来自导入包,需与文档标题一样转义后再进入错误消息。
			if errors.Is(err, errImportedTreeBoxLocked) {
				err = errors.New(fmt.Sprintf(Conf.Language(388), htmlstd.EscapeString(finalRelPath)))
			}
			return
		}
		tree.Path = finalRelPath
	}

	// 读取包内自定义排序,复制成功后根据目标位置重新编号
	sourceSortIDs := map[string]int{}
	var sortErr error
	sortPath := filepath.Join(unzipRootPath, ".siyuan", "sort.json")
	if filelock.IsExist(sortPath) {
		sourceSortIDs, sortErr = readSortConfMap(sortPath)
		if nil != sortErr {
			logging.LogErrorf("read import sort conf failed: %s", sortErr)
			sourceSortIDs = map[string]int{}
		}
		if removeErr := os.RemoveAll(sortPath); nil != removeErr {
			logging.LogErrorf("remove temp sort conf failed: %s", removeErr)

View on GitHub (pinned to 9f775e8a12)