{"record":{"id":"0e8e8f66d287d910","repo":"siyuan-note/siyuan","slug":"please-unlock-the-encrypted-notebook-first-0e8e8f","errorCode":null,"errorMessage":"Please unlock the encrypted notebook first","messagePattern":"Please unlock the encrypted notebook first","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/history_diff.go","lineNumber":326,"sourceCode":"\nfunc readCurrentDocVersionData(blockTree *treenode.BlockTree) (ret []byte, err error) {\n\trelPath, err := filesys.ValidateBoxRelativePath(blockTree.BoxID, blockTree.Path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tencrypted := IsEncryptedBox(blockTree.BoxID)\n\tif encrypted {\n\t\tHoldBoxReadLock(blockTree.BoxID)\n\t\tdefer ReleaseBoxReadLock(blockTree.BoxID)\n\t}\n\tabsPath := filepath.Join(util.DataDir, blockTree.BoxID, filepath.FromSlash(relPath))\n\tret, err = filelock.ReadFile(absPath)\n\tif err != nil || !encrypted {\n\t\treturn\n\t}\n\tdek, err := GetDEKIfUnlocked(blockTree.BoxID)\n\tif err != nil {\n\t\treturn nil, errors.New(Conf.Language(314))\n\t}\n\tret, err = DecryptFile(blockTree.BoxID, relPath, dek, ret)\n\treturn\n}\n\nfunc loadHistoryDocVersion(historyPath string) (ret *loadedDocVersion, err error) {\n\tabsPath, err := validateHistoryPath(historyPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !strings.HasSuffix(strings.ToLower(absPath), \".sy\") {\n\t\treturn nil, errors.New(\"history version is not a document\")\n\t}\n\trelPath, err := filepath.Rel(util.HistoryDir, absPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tparts := strings.SplitN(filepath.ToSlash(relPath), \"/\", 3)","sourceCodeStart":308,"sourceCodeEnd":344,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/afa823b6b4e4f183511e0bc0a3be93caa94c7c97/kernel/model/history_diff.go#L308-L344","documentation":"Reading the current version of a document in an encrypted notebook requires that notebook's data-encryption key (DEK) to be resident in memory. GetDEKIfUnlocked failed - the box is locked - so the kernel returns the localized message 'Please unlock the encrypted notebook first' (Conf.Language(314)) instead of decrypting. The read lock is held during the attempt; without an unlocked DEK the content is inaccessible by design.","triggerScenarios":"Diffing the current version of a document in an encrypted notebook that was not unlocked in this kernel session; unlock expired after kernel restart or relock; a different encrypted box was unlocked than the one containing the doc.","commonSituations":"Kernel restart clears unlocked keys, then an old UI tab retries a diff; headless scripts calling the API without completing the unlock flow; multiple encrypted boxes where only one was unlocked.","solutions":["Unlock the encrypted notebook in the UI (or via the unlock API), then retry the diff","Verify you unlocked the box that actually contains the document (box ID from the doc's path)","For automated runs, complete the unlock step first or keep encrypted boxes out of the flow"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// kernel/plugin-side: probe unlock state before diffing\nif model.IsEncryptedBox(boxID) {\n    if _,dekErr := model.GetDEKIfUnlocked(boxID); dekErr != nil {\n        // run the unlock flow, then retry\n    }\n}","typeGuard":null,"tryCatchPattern":"diff, err := model.DiffDocVersions(left, right)\nif err != nil && err.Error() == model.Conf.Language(314) {\n    // prompt unlock, wait for completion, retry the diff once\n}","preventionTips":["Unlock encrypted boxes at session start before any read APIs are used","Treat the localized unlock message as a retryable state, not a permanent failure"],"tags":["go","siyuan","encryption","locked","notebook"],"backgroundTag":"encrypted-storage-locked","analyzedSha":"afa823b6b4e4f183511e0bc0a3be93caa94c7c97","analyzedAt":"2026-08-18T17:04:10.865Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}