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
- Unlock the target encrypted notebook (enter its passphrase via the unlock dialog/API) to obtain a decryption lease, then retry the import.
- If the notebook should not be encrypted, decrypt/remove encryption on the target notebook before importing.
- 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).
- 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
- Unlock encrypted notebooks before batch imports and keep the session alive
- Re-unlock after kernel restarts — the lease does not survive reboot
- Prefer importing into an unencrypted staging notebook for automation
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
- encrypted notebook [ ] has no valid identity
- invalid imported notebook identity
- notebook [ ] has conflicting normal and encrypted identities
- 199
- block not found or its encrypted notebook is locked
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)