{"record":{"id":"3730f4a624990daf","repo":"siyuan-note/siyuan","slug":"encrypted-repository-data-has-no-matching-notebook","errorCode":null,"errorMessage":"encrypted repository data has no matching notebook [%s]","messagePattern":"encrypted repository data has no matching notebook \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/repository.go","lineNumber":731,"sourceCode":"\n// decryptRepoDataIfNeeded 判断仓库数据是否属于加密笔记本，如果是则按路径类型分流解密。\n// file.Path 格式：/<boxID>/...\n// .sy → DecryptFile，assets/* → DecryptAsset，storage/av/*.json → av.DecryptAVData。\n// 密文缺少有效路径上下文、笔记本未解锁或认证失败时返回错误，不允许调用方按明文继续处理。\nfunc decryptRepoDataIfNeeded(data []byte, filePath string) ([]byte, error) {\n\trelPath := strings.TrimPrefix(filePath, \"/\")\n\tparts := strings.SplitN(relPath, \"/\", 2)\n\tencryptedPayload := util.IsCiphertext(data) || bytes.HasPrefix(data, encryptedAssetMagic)\n\tif len(parts) < 2 || !ast.IsNodeIDPattern(parts[0]) {\n\t\tif encryptedPayload {\n\t\t\treturn nil, errors.New(\"encrypted repository data is missing notebook context\")\n\t\t}\n\t\treturn data, nil\n\t}\n\tboxID := parts[0]\n\tif !IsEncryptedBox(boxID) {\n\t\tif encryptedPayload {\n\t\t\treturn nil, fmt.Errorf(\"encrypted repository data has no matching notebook [%s]\", boxID)\n\t\t}\n\t\treturn data, nil\n\t}\n\t// 持读锁，防止 LockBox 在解密期间清 DEK/缓存\n\tHoldBoxReadLock(boxID)\n\tdefer ReleaseBoxReadLock(boxID)\n\tdek, err := GetDEKIfUnlocked(boxID)\n\tif err != nil {\n\t\treturn nil, errors.New(Conf.Language(314))\n\t}\n\tboxRelPath := parts[1]\n\t// 按路径类型分流\n\tif strings.HasPrefix(boxRelPath, \"assets/\") {\n\t\tdiskName := filepath.Base(boxRelPath)\n\t\tplain, decErr := DecryptAsset(boxID, diskName, dek, data)\n\t\tif decErr != nil {\n\t\t\treturn nil, decErr\n\t\t}","sourceCodeStart":713,"sourceCodeEnd":749,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/repository.go#L713-L749","documentation":"decryptRepoDataIfNeeded found a syntactically valid boxID in the path, but IsEncryptedBox(boxID) is false while the payload is ciphertext — the notebook the path points to is not the encrypted notebook that owns this data. It returns 'encrypted repository data has no matching notebook [%s]' naming the offending boxID.","triggerScenarios":"Reading a repo file whose path's first segment parses as a node ID but maps to a plain (non-encrypted) notebook, a deleted notebook, or a box whose encryption metadata is absent, while the payload itself is ciphertext.","commonSituations":"Copying snapshot data between workspaces with different notebooks; notebooks converted from encrypted to plain (or re-created with the same name but new ID) leaving stale history entries; synced snapshots arriving before the encrypted notebook's metadata; typo'd or fabricated paths in API scripts.","solutions":["Confirm the notebook with that ID exists and is configured as encrypted (IsEncryptedBox); restore it if deleted","If the notebook was intentionally de-encrypted, re-snapshot or remove the stale encrypted entries","Sync completely so the encrypted box's metadata exists locally before reading its history","Verify the boxID used in the request path actually matches the snapshot's owner notebook"],"exampleFix":"// before: guessing the box ID from a renamed notebook\nawait fetchPost('/api/repo/getRepoFile', { path: oldBoxID + '/assets/img.png' });\n// after: resolve the current box ID from the notebook list first\nconst nbs = await fetchPost('/api/notebook/lsNotebooks', {});\nconst box = nbs.data.notebooks.find(n => n.name === 'My Encrypted Notebook');\nawait fetchPost('/api/repo/getRepoFile', { path: box.id + '/assets/img.png' });","handlingStrategy":"validation","validationCode":"const nbs = await fetchPost('/api/notebook/lsNotebooks', {});\nconst boxID = repoPath.replace(/^\\//, '').split('/')[0];\nif (!nbs.data.notebooks.some(n => n.id === boxID)) {\n  throw new Error('Notebook ' + boxID + ' does not exist locally');\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await fetchPost('/api/repo/getRepoFile', { path: repoPath });\n} catch (e) {\n  if (String(e).includes('no matching notebook')) await restoreNotebookThenRetry(boxID);\n  else throw e;\n}","preventionTips":["Complete sync before reading another device's snapshots","Avoid deleting/renaming encrypted notebooks without cleaning their history","Match snapshots to current notebook IDs instead of reusing stale IDs","Verify boxID existence in scripts before repo file calls"],"tags":["repository","encryption","notebook","sync"],"backgroundTag":"resource-not-found","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}