siyuan-note/siyuan · error

encrypted repository data is missing notebook context

Error message

encrypted repository data is missing notebook context

What it means

decryptRepoDataIfNeeded rejects encrypted payloads (ciphertext or encrypted-asset magic) when the file path cannot be split into '<boxID>/<relpath>' with a valid node-ID boxID. Without a well-formed notebook path prefix there is no way to select the right notebook key, so it returns 'encrypted repository data is missing notebook context'.

Solutions

  1. Pass the full repo-relative path including the notebook-ID first segment to the repo API
  2. Re-create or re-index the affected snapshot so paths carry the boxID prefix
  3. If the encrypted payload legitimately has no notebook context, this data is unreadable by design — remove it from the repo or re-store it unencrypted
  4. Verify callers don't TrimPrefix or rewrite the path before invoking repo file APIs

Example fix

// before: path lost the notebook prefix
await fetchPost('/api/repo/getRepoFile', { path: 'assets/foo.png' });
// after: keep the boxID segment
await fetchPost('/api/repo/getRepoFile', { path: '20240101120000-abcdefg/assets/foo.png' });
Defensive patterns

Strategy: validation

Validate before calling

const rel = repoPath.replace(/^\//, '');
const m = rel.match(/^([0-9]{14}-[0-9a-z]{7})\//);
if (!m) throw new Error('Encrypted repo path needs a notebook-ID prefix: ' + repoPath);

Type guard

const hasBoxPrefix = (p) => /^\/?[0-9]{14}-[0-9a-z]{7}\//.test(p);

Try / catch

try {
  return await fetchPost('/api/repo/getRepoFile', { path: repoPath });
} catch (e) {
  if (String(e).includes('missing notebook context')) logUnreadableEntry(repoPath);
  else throw e;
}

Prevention

When it happens

Trigger: Calling any path-based repo reader (GetRepoFile, OpenRepoSnapshotFile, RollbackRepoSnapshotFile, ExportRepoFile, parseTitleInSnapshot) with an encrypted payload whose path has no directory prefix or whose first segment is not a SiYuan node-ID pattern (e.g. root-level files like 'index.json' that are somehow encrypted).

Common situations: Snapshots containing top-level non-document files that were encrypted by mistake; tooling that strips the first path component before calling the API; manually constructed repo paths that omit the notebook-ID segment; legacy snapshots from format transitions.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/137d2d4384429836. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/repository.go:724

		}

		title = tree.Root.IALAttr("title")
		rootID = tree.Root.ID
	}
	return
}

// 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]

View on GitHub (pinned to 9f775e8a12)