siyuan-note/siyuan · error

conditional replacement of encrypted attribute views is not…

Error message

conditional replacement of encrypted attribute views is not supported

What it means

Encrypted notebooks store attribute views under a box-scoped path, and the conditional (compare-and-swap) write helper util.WriteFileIfUnchanged only supports the global path scheme. When a conditional save (original != nil) targets an AV in an encrypted box (avBoxID != ""), the code refuses with this error because conditional replacement semantics are not implemented for that layout.

Solutions

  1. Use unconditional SaveAttributeView for attribute views in encrypted notebooks
  2. Detect encrypted-box AVs (avBoxID != "") and route them to the unconditional save path in your code
  3. If conditional replacement is required for encrypted boxes, it needs kernel support — file/track a feature request rather than bypassing the check

Example fix

// before
err = av.SaveAttributeViewIfUnchanged(attrView, original) // fails in encrypted box
// after
if boxID != "" {
  err = av.SaveAttributeView(attrView)
} else {
  err = av.SaveAttributeViewIfUnchanged(attrView, original)
}
Defensive patterns

Strategy: fallback

Validate before calling

if boxID != "" && original != nil {
  // encrypted-box AV: conditional save unsupported, plan to use SaveAttributeView
}

Try / catch

if err := avlib.SaveAttributeViewIfUnchanged(attrView, original); err != nil {
  if strings.Contains(err.Error(), "conditional replacement") {
    err = avlib.SaveAttributeView(attrView) // unconditional save works everywhere
  }
}

Prevention

When it happens

Trigger: Calling SaveAttributeViewIfUnchanged on an attribute view that belongs to an encrypted notebook (non-empty box ID); automation doing optimistic-concurrency saves against encrypted-box databases; migration scripts using the conditional API across all AVs indiscriminately.

Common situations: Users with encrypted notebooks running plugins/scripts that use the newer conditional save API; batch jobs applying SaveAttributeViewIfUnchanged over every AV in the workspace; code paths that resolve boxID after deciding to do a conditional save.

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


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

Appendix: source

Thrown at kernel/av/av.go:1041

	}
	restoreResolvedColors()
	restoreRenderedContents()
	if err != nil {
		logging.LogErrorf("marshal attribute view [%s] failed: %s", av.ID, err)
		return
	}

	// 缓存与待写入数据一致时跳过落盘;缓存未命中时再读盘比对,避免无变更的重复写入
	// 通过 fallback 查找 AV 定义的实际路径(普通 box 全局,加密笔记本笔记本级)
	avJSONPath, avBoxID := FindAttributeViewPath(av.ID)
	if avJSONPath == "" {
		// 文件不存在(首次创建),使用全局路径,boxID 为空(普通 box)
		// 加密笔记本的首次创建由 handler 层通过 SetAVBoxID 预设路径
		avJSONPath = GetAttributeViewDataPath(av.ID)
	}
	if original != nil {
		if avBoxID != "" {
			return errors.New("conditional replacement of encrypted attribute views is not supported")
		}
		if err = util.WriteFileIfUnchanged(avJSONPath, original, data); err != nil {
			return err
		}
		cacheAttributeViewData(av, avBoxID, data)
		if RichTextSpec <= av.Spec {
			NotifyAttributeViewSaved(av.ID, avBoxID)
		}
		return nil
	}
	if cachedData, version, ok := cache.GetAVDataWithVersionInBox(av.ID, avBoxID); ok {
		if len(cachedData) == len(data) && bytes.Equal(cachedData, data) {
			cache.SetAVSearchDataInBox(av.ID, avBoxID, version, newAttributeViewSearchInfo(av))
			return
		}
	} else {
		if diskData, readErr := filelock.ReadFile(avJSONPath); nil == readErr {
			// 加密笔记本的磁盘数据是密文,需先解密再比对

View on GitHub (pinned to 9f775e8a12)