siyuan-note/siyuan · error

attribute view history context is ambiguous [%s]

Error message

attribute view history context is ambiguous [%s]

What it means

Thrown by ResolveHistoryAttributeViewBoxID when resolving which encrypted notebook owns a historical attribute view. After globbing history directories whose name starts with the formatted 'created' timestamp and scanning each for a file storage/av/<avID>.json (both at the global level and inside each notebook subdirectory), more than one distinct source context matched. The resolver requires exactly one match so it can decide whether an encrypted box key is needed.

Source

Thrown at kernel/model/attribute_view_render.go:1103

		if gulu.File.IsExist(filepath.Join(historyDir, "storage", "av", avID+".json")) {
			boxIDs = append(boxIDs, "")
		}
		entries, _ := os.ReadDir(historyDir)
		for _, entry := range entries {
			if !entry.IsDir() || !ast.IsNodeIDPattern(entry.Name()) {
				continue
			}
			candidate := filepath.Join(historyDir, entry.Name(), "storage", "av", avID+".json")
			if gulu.File.IsExist(candidate) {
				boxIDs = append(boxIDs, entry.Name())
			}
		}
	}
	if len(boxIDs) == 0 {
		return "", av.ErrAttributeViewNotFound
	}
	if len(boxIDs) != 1 {
		return "", fmt.Errorf("attribute view history context is ambiguous [%s]", avID)
	}
	if IsEncryptedBox(boxIDs[0]) {
		return boxIDs[0], nil
	}
	return "", nil
}

func RenderRepoSnapshotAttributeView(indexID, avID, viewID, carrierViewID string) (viewable av.Viewable, attrView *av.AttributeView, err error) {
	if !ast.IsNodeIDPattern(avID) {
		err = ErrInvalidID
		return
	}

	repo, err := newRepository()
	if err != nil {
		return
	}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Inspect the matched history directory (named <created>-*) and delete or deduplicate the stray storage/av/<avID>.json so only one owner remains.
  2. If the duplicate is legitimate (e.g. two notebooks legitimately referencing one AV), restructure so the AV lives in exactly one storage location and the other references point to it.
  3. Regenerate the history snapshot after deduplication so the resolve path finds a single candidate.
Defensive patterns

Strategy: validation

Validate before calling

// Before rendering, ensure the AV resolves to exactly one history source.
boxID, err := model.ResolveHistoryAttributeViewBoxID(avID, created)
if err != nil {
    // surface 'ambiguous' / 'not found' to the user; do not call render
    return err
}
// boxID is "" for global plaintext or a boxID for encrypted; safe to proceed

Try / catch

if err := model.ResolveHistoryAttributeViewBoxID(avID, created); err != nil {
    if strings.Contains(err.Error(), "ambiguous") {
        // prompt user to deduplicate the AV across notebooks/global storage
    }
    return err
}

Prevention

When it happens

Trigger: Calling the history-attribute-view resolve/render flow (API renderHistoryAttributeView) with an avID that exists in more than one place within the matching history snapshot: simultaneously in global storage/av/ and inside a notebook <boxID>/storage/av/, or inside two different notebook subdirectories. Duplicate or mirrored AV files in the history tree make the owner ambiguous.

Common situations: A synced or imported workspace where an attribute view was copied between the global storage and a notebook, or where two notebooks each hold a file with the same avID. History snapshots taken right after such a copy produce multiple candidates. AV IDs are expected globally unique; a collision usually indicates duplicated data.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/d1d2bab4eb6a46f2. Report an issue: GitHub.