siyuan-note/siyuan · error
encrypted repository data has no matching notebook
Error message
encrypted repository data has no matching notebook [%s]
What it means
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.
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
Example fix
// before: guessing the box ID from a renamed notebook
await fetchPost('/api/repo/getRepoFile', { path: oldBoxID + '/assets/img.png' });
// after: resolve the current box ID from the notebook list first
const nbs = await fetchPost('/api/notebook/lsNotebooks', {});
const box = nbs.data.notebooks.find(n => n.name === 'My Encrypted Notebook');
await fetchPost('/api/repo/getRepoFile', { path: box.id + '/assets/img.png' }); Defensive patterns
Strategy: validation
Validate before calling
const nbs = await fetchPost('/api/notebook/lsNotebooks', {});
const boxID = repoPath.replace(/^\//, '').split('/')[0];
if (!nbs.data.notebooks.some(n => n.id === boxID)) {
throw new Error('Notebook ' + boxID + ' does not exist locally');
} Try / catch
try {
return await fetchPost('/api/repo/getRepoFile', { path: repoPath });
} catch (e) {
if (String(e).includes('no matching notebook')) await restoreNotebookThenRetry(boxID);
else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- encrypted repository data is missing notebook context
- encrypted repository data is missing valid notebook context
- cannot replay block swap across encrypted notebook…
- cannot swap blocks across encrypted notebook boundaries
- CLI does not support encrypted notebook
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/3730f4a624990daf.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/repository.go:731
// decryptRepoDataIfNeeded 判断仓库数据是否属于加密笔记本,如果是则按路径类型分流解密。
// file.Path 格式:/<boxID>/...
// .sy → DecryptFile,assets/* → DecryptAsset,storage/av/*.json → av.DecryptAVData。
// 密文缺少有效路径上下文、笔记本未解锁或认证失败时返回错误,不允许调用方按明文继续处理。
func decryptRepoDataIfNeeded(data []byte, filePath string) ([]byte, error) {
relPath := strings.TrimPrefix(filePath, "/")
parts := strings.SplitN(relPath, "/", 2)
encryptedPayload := util.IsCiphertext(data) || bytes.HasPrefix(data, encryptedAssetMagic)
if len(parts) < 2 || !ast.IsNodeIDPattern(parts[0]) {
if encryptedPayload {
return nil, errors.New("encrypted repository data is missing notebook context")
}
return data, nil
}
boxID := parts[0]
if !IsEncryptedBox(boxID) {
if encryptedPayload {
return nil, fmt.Errorf("encrypted repository data has no matching notebook [%s]", boxID)
}
return data, nil
}
// 持读锁,防止 LockBox 在解密期间清 DEK/缓存
HoldBoxReadLock(boxID)
defer ReleaseBoxReadLock(boxID)
dek, err := GetDEKIfUnlocked(boxID)
if err != nil {
return nil, errors.New(Conf.Language(314))
}
boxRelPath := parts[1]
// 按路径类型分流
if strings.HasPrefix(boxRelPath, "assets/") {
diskName := filepath.Base(boxRelPath)
plain, decErr := DecryptAsset(boxID, diskName, dek, data)
if decErr != nil {
return nil, decErr
}View on GitHub (pinned to 9f775e8a12)