siyuan-note/siyuan · warning

OCR metadata changed during asset relink

Error message

OCR metadata changed during asset relink

What it means

Save captures a snapshot of the in-memory OCR metadata (assetsTexts) when the plan was prepared, then re-acquires the lock before writing. If the in-memory map changed between PrepareAssetTextRelinks and Save (maps.Equal fails), the plan is stale and Save aborts with "OCR metadata changed during asset relink" to avoid clobbering concurrent updates with data computed from an outdated snapshot.

Solutions

  1. Discard the stale plan and re-run PrepareAssetTextRelinks, then call Save promptly on the fresh plan.
  2. Retry the whole prepare→save sequence with a small backoff; it succeeds once concurrent OCR writes settle.
  3. Avoid invoking asset relink while OCR recognition is running (wait for indexing to finish).
  4. Serialize relink operations through a single caller so two relinks cannot race.

Example fix

// before: hold a plan for a long time then save
plan, _ := util.PrepareAssetTextRelinks(mappings)
// ... long UI wait, OCR runs meanwhile ...
plan.Save(historyDir, selected) // stale snapshot
// after: re-prepare right before saving
plan, err := util.PrepareAssetTextRelinks(mappings)
if err == nil {
    plan.Save(historyDir, selected)
}
Defensive patterns

Strategy: retry

Try / catch

for attempt := 0; attempt < 3; attempt++ {
    plan, err := util.PrepareAssetTextRelinks(mappings)
    if err != nil {
        return err
    }
    if _, err := plan.Save(historyDir, selected); err == nil || err.Error() != "OCR metadata changed during asset relink" {
        return err
    }
    time.Sleep(time.Duration(attempt+1) * 100 * time.Millisecond)
}

Prevention

When it happens

Trigger: Another goroutine modified the in-memory OCR metadata (e.g. via OCR recognition of new images, asset deletion cleanup, or a concurrent relink Save that replaced assetsTexts) between calling PrepareAssetTextRelinks and calling plan.Save on the same plan.

Common situations: Running asset rename/relink while background OCR indexing is still processing images; two concurrent relink operations; an OCR re-scan triggered by the user while a rename dialog's save was in flight.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at kernel/util/ocr_relink_batch.go:144

			return nil, errors.New("invalid OCR mapping index")
		}
		result := p.Results[i]
		if result.Reason != "" {
			return nil, errors.New(result.Reason)
		}
		if !result.Changed {
			continue
		}
		texts[(&url.URL{Path: p.mappings[i].NewPath}).EscapedPath()] = p.values[i]
		changed = append(changed, i)
	}
	if len(changed) == 0 {
		return nil, nil
	}
	assetsTextsLock.Lock()
	defer assetsTextsLock.Unlock()
	if !maps.Equal(p.memory, assetsTexts) {
		return nil, errors.New("OCR metadata changed during asset relink")
	}
	backup, err := json.MarshalIndent(p.texts, "", "  ")
	if err != nil {
		return nil, err
	}
	history := filepath.Join(historyDir, "assets", "ocr-texts.json")
	if err = os.MkdirAll(filepath.Dir(history), 0755); err != nil {
		return nil, err
	}
	if err = gulu.File.WriteFileSafer(history, backup, 0644); err != nil {
		return nil, err
	}
	data, err := json.MarshalIndent(texts, "", "  ")
	if err != nil {
		return nil, err
	}
	if err = WriteFileIfUnchanged(p.path, p.original, data); err != nil {
		return nil, err

View on GitHub (pinned to 9f775e8a12)