siyuan-note/siyuan · error
Conf.Language(0) (localized open notebook failure message)
Error message
Conf.Language(0) (localized open notebook failure message)
What it means
When resolving an asset that is not under the global assets/ prefix, getAssetAbsPath searches the assets folder of the document's owning notebook; it first calls ListNotebooks to enumerate open notebooks. If that enumeration fails (e.g. the workspace data directory cannot be read), the function aborts with Conf.Language(0), the localized generic "open notebook failed" message shown to users.
Source
Thrown at kernel/model/assets.go:1313
if rootEvalErr != nil {
return "", fmt.Errorf("resolve assets root [%s] failed: %w", assetsRoot, rootEvalErr)
}
if !gulu.File.IsSubPath(realAssetsRoot, realP) {
return "", fmt.Errorf("symlink [%s] resolves outside data/assets: [%s]", p, realP)
}
// 安全校验使用解析后的路径,返回原路径以便下游与 DataDir 保持同一路径形式
return p, nil
}
return p, nil
}
// 在文档同级 assets 文件夹下搜索
if !strings.HasPrefix(relativePath, "assets/") {
return "", nil
}
notebooks, err := ListNotebooks()
if err != nil {
return "", errors.New(Conf.Language(0))
}
for _, notebook := range notebooks {
if !includeEncrypted && IsEncryptedBox(notebook.ID) {
continue // 加密笔记本的资源不参与全局路径解析(孤岛,资源不跨边界)
}
notebookAbsPath := filepath.Join(util.DataDir, notebook.ID)
filelock.Walk(notebookAbsPath, func(path string, d fs.DirEntry, err error) error {
if isSkipFile(d.Name()) {
if d.IsDir() {
return filepath.SkipDir
}
return nil
}
if p := filepath.ToSlash(path); strings.HasSuffix(p, relativePath) {
if gulu.File.IsExist(path) {
absPath = path
return fs.SkipAll
}View on GitHub (pinned to 8641553a1f)
Solutions
- Check kernel logs for the underlying ListNotebooks error and fix the unreadable/invalid notebook directory it reports.
- Verify the workspace data directory exists and is readable/writable by the kernel process.
- Restart the kernel so notebook scanning runs again after the filesystem issue is fixed.
- If a specific notebook folder is corrupt, remove or repair it (e.g. restore from sync/backup) so enumeration succeeds.
Defensive patterns
Strategy: retry
Try / catch
try {
const resp = await fetch('/api/asset/...');
const data = await resp.json();
} catch (e) {
// msg is the localized "open notebook failed" text; check kernel logs for the real cause
await new Promise(r => setTimeout(r, 1000)); // retry after kernel finishes booting
} Prevention
- Verify the workspace data directory is readable before calling asset APIs
- Don't call asset resolution during kernel startup before notebooks are indexed
- Watch kernel logs for ListNotebooks errors after failed sync/restore operations
- Repair or remove malformed notebook directories promptly
When it happens
Trigger: Calling GetAssetAbsPathWithOpt with a relativePath starting with "assets/" while ListNotebooks errors — i.e. the data directory is unreadable, a notebook ID directory is malformed, or workspace state is inconsistent at the moment of the call.
Common situations: Corrupted or partially-synced workspace data dir; notebook folders with invalid IDs after a failed sync/restore; permission problems on the data directory; calling the API during startup before notebooks are indexed.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- asset path does not belong to a notebook: %s
- resolve assets directory [%s] failed: %w
- copy unused asset [%s] to history failed: %w
- remove unused asset [%s] failed: %w
- walk notebook assets [%s] failed: %w
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/2e8ebcd89a217e60.
Report an issue: GitHub.