{"record":{"id":"de0a067d744c49b8","repo":"siyuan-note/siyuan","slug":"write-notebook-crypt-backup-failed-w","errorCode":null,"errorMessage":"write notebook crypt backup failed: %w","messagePattern":"write notebook crypt backup failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/model/crypto.go","lineNumber":2536,"sourceCode":"// writeNotebookCryptBackup 写入加密笔记本的 BoxCrypt 备份。\n// 仅在 Encrypted=true 的笔记本上调用，配合 CreateEncryptedBox / ChangeMasterPassword 写入。\nfunc writeNotebookCryptBackup(boxID string, crypt *conf.BoxEncryption) error {\n\tif !ast.IsNodeIDPattern(boxID) {\n\t\treturn errors.New(\"invalid notebook ID\")\n\t}\n\tif err := validateBoxEncryption(crypt); err != nil {\n\t\treturn err\n\t}\n\tbackupPath := notebookCryptoBackupPath(boxID)\n\tif err := os.MkdirAll(filepath.Dir(backupPath), 0755); err != nil {\n\t\treturn fmt.Errorf(\"mkdir notebook crypt backup dir failed: %w\", err)\n\t}\n\tdata, err := gulu.JSON.MarshalIndentJSON(crypt, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal notebook crypt backup failed: %w\", err)\n\t}\n\tif err := filelock.WriteFile(backupPath, data); err != nil {\n\t\treturn fmt.Errorf(\"write notebook crypt backup failed: %w\", err)\n\t}\n\treturn nil\n}\n\n// readNotebookCryptBackup 读取加密笔记本的 BoxCrypt 备份。\n// 备份文件不存在时返回 (nil, nil)，调用方据此区分\"非加密笔记本\"和\"备份不存在\"。\nfunc readNotebookCryptBackup(boxID string) (*conf.BoxEncryption, error) {\n\tif !ast.IsNodeIDPattern(boxID) {\n\t\treturn nil, errors.New(\"invalid notebook ID\")\n\t}\n\tbackupPath := notebookCryptoBackupPath(boxID)\n\tif !filelock.IsExist(backupPath) {\n\t\treturn nil, nil\n\t}\n\treturn readBoxEncryptionFile(backupPath)\n}\n\nfunc readBoxEncryptionFile(backupPath string) (*conf.BoxEncryption, error) {","sourceCodeStart":2518,"sourceCodeEnd":2554,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/crypto.go#L2518-L2554","documentation":"The final step of writeNotebookCryptBackup writes the marshaled JSON to the backup path via filelock.WriteFile, wrapping any failure with this message. The backup file holds the notebook's key-envelope material, so a failure here means the encrypted notebook's recovery backup was not persisted.","triggerScenarios":"filelock.WriteFile(backupPath, data) fails during CreateEncryptedBox or ChangeMasterPassword — read-only directory, disk full, the backup path locked by another process holding the filelock, antivirus interference, or the target existing as a directory.","commonSituations":"Docker volume mounted read-only; two SiYuan instances sharing one workspace contending on the file lock; disk quota exceeded; enterprise antivirus blocking writes to newly created files.","solutions":["Inspect the wrapped error and fix the underlying write failure (permissions, disk space, lock contention)","Ensure only one SiYuan instance uses the workspace at a time","Verify the backup directory exists, is writable, and that backupPath is not a directory","Retry the operation after resolving the I/O issue — the notebook encryption change should not be considered complete without this backup"],"exampleFix":"// before\nerr := model.ChangeMasterPassword(box, old, new) // fails: write notebook crypt backup failed\n// after\n# free disk space / fix permissions, ensure single instance, then:\nerr := model.ChangeMasterPassword(box, old, new)\n# verify the backup file exists under the notebook crypt backup path","handlingStrategy":"try-catch","validationCode":"// pre-check backup dir is writable\ninfo, err := os.Stat(backupDir)\nif err != nil || !info.IsDir() { return fmt.Errorf(\"backup dir missing\") }\nif err := os.WriteFile(filepath.Join(backupDir, \".probe\"), nil, 0644); err != nil {\n    return fmt.Errorf(\"backup dir not writable: %w\", err)\n}\nos.Remove(filepath.Join(backupDir, \".probe\"))","typeGuard":null,"tryCatchPattern":"if err := model.ChangeMasterPassword(box, old, new); err != nil {\n    if strings.Contains(err.Error(), \"write notebook crypt backup\") {\n        // recovery backup missing: surface loudly, do not treat as success\n        return fmt.Errorf(\"password changed but recovery backup not written: %w\", err)\n    }\n    return err\n}","preventionTips":["Run a single SiYuan instance per workspace to avoid filelock contention","Confirm the backup file exists after any CreateEncryptedBox/ChangeMasterPassword call","Exclude the workspace from antivirus real-time locking where possible"],"tags":["filesystem","file-write","backup","encryption"],"backgroundTag":"file-write-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}