siyuan-note/siyuan · error
Conf.Language(390)
Error message
Conf.Language(390)
What it means
setNodeAttrs0 rejects setting the 'bookmark' or 'tags' attribute (case-insensitive) on a block inside an encrypted notebook, returning Conf.Language(390): 'Encrypted notebooks do not support bookmarks and tags, please remove the %s attribute of this block first'. Encrypted notebooks are isolated islands without the global SQLite aggregation these attributes depend on.
Solutions
- Remove the 'bookmark'/'tags' keys from the attribute map before calling when the target box is encrypted
- Keep bookmark/tag workflows out of encrypted notebooks; use unencrypted notebooks for those blocks
- Partition your batch by notebook: apply full attrs to normal notebooks, filtered attrs to encrypted ones
- Check the box type first (e.g. via workspace notebook list / IsEncryptedBox semantics) and warn the user instead of failing
Example fix
// before
await fetchPost('/api/attr/setBlockAttrs', {id, attrs: {bookmark: 'b', custom: 'x'}});
// after
if (isEncryptedBox(boxOf(id))) delete attrs.bookmark, delete attrs.tags;
await fetchPost('/api/attr/setBlockAttrs', {id, attrs}); Defensive patterns
Strategy: validation
Validate before calling
const FORBIDDEN = ['bookmark', 'tags'];
for (const k of Object.keys(attrs)) {
if (FORBIDDEN.includes(k.toLowerCase()) && isEncryptedBox(boxId)) delete attrs[k];
} Type guard
null
Try / catch
try { await setBlockAttrs(id, attrs); } catch (e) { if (/bookmark|tags/.test(String(e))) { delete forbiddenKeys(attrs); await setBlockAttrs(id, attrs); } else throw e; } Prevention
- Filter bookmark/tags keys when targeting encrypted notebooks
- Check notebook encryption status before cross-notebook attr copy
- Warn users that bookmarks/tags are unavailable in encrypted notebooks
- Partition batch operations by notebook type
When it happens
Trigger: POST /api/attr/setBlockAttrs (also batchSetBlockAttrs, setNodeAttrs with tx, or any path like updateBoundBlockAvsAttribute / doRemoveAttrViewView that funnels into setNodeAttrs0) with nameValues containing key 'bookmark' or 'tags' (any casing) while the block's box is an encrypted box (IsBoxEncrypted(boxID)).
Common situations: Plugin or script bulk-setting custom+standard attributes across all notebooks, some encrypted; drag/drop or sync flows copying attrs into encrypted notebooks; attribute-view bound blocks trying to write tags.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Conf.Language(313) (localized message: bookmark/tag not…
- Encrypted notebooks do not support this operation
- Encrypted notebooks do not support this operation
- Please unlock the encrypted notebook first
- The shorthand save notebook is unavailable. Select an…
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/b610d66990d397a8.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/blockial.go:370
if oldAttrs[DocSortModeAttr] != newAttrs[DocSortModeAttr] && ast.NodeDocument == node.Type {
cache.RemoveDocIALInBox(tree.Path, tree.Box)
cache.PutDocIALInBox(tree.Path, tree.Box, newAttrs)
}
pushBlockAttrs(oldAttrs, node)
pushDocSortModeChanged(oldAttrs, node, tree)
return
}
func setNodeAttrs0(node *ast.Node, nameValues map[string]string, boxID string) (oldAttrs map[string]string, err error) {
if err = treenode.ValidateTabsAttrs(node, nameValues); nil != err {
return
}
// 加密笔记本不支持书签和标签(依赖全局 SQLite 聚合,加密笔记本是孤岛)
if IsEncryptedBox(boxID) && boxID != "" {
for name := range nameValues {
switch strings.ToLower(name) {
case "bookmark", "tags":
err = errors.New(fmt.Sprintf(Conf.Language(390), name))
return
}
}
}
oldAttrs = parse.IAL2Map(node.KramdownIAL)
newAttrsUnEsc := parse.IAL2MapUnEsc(node.KramdownIAL)
for name := range nameValues {
if "fold" == strings.ToLower(name) {
delete(newAttrsUnEsc, "heading-fold")
break
}
}
for name, value := range nameValues {
value = util.RemoveInvalidRetainCtrl(value)
value = strings.TrimSpace(value)
lowerName := strings.ToLower(name)
// 转换为小写再验证属性名View on GitHub (pinned to 9f775e8a12)