siyuan-note/siyuan · error

unmarshal legacy AI editor actions failed: %w

Error message

unmarshal legacy AI editor actions failed: %w

What it means

During legacy migration, the localStorage key "local-ai" exists and holds a STRING, but that string is not valid JSON for the expected shape []*legacyAIEditorAction{Name,Memo}. gulu.JSON.UnmarshalJSON returned a syntax or type error, so migration aborts and every Get/Save/Remove AI editor action call fails until the value is fixed.

Source

Thrown at kernel/model/ai_editor.go:215

		return fmt.Errorf("marshal AI editor actions failed: %w", err)
	}
	if err = filelock.WriteFile(aiEditorActionsPath(), bytes); err != nil {
		return fmt.Errorf("write AI editor actions failed: %w", err)
	}
	return nil
}

func migrateLegacyAIEditorActions(data *aiEditorActionsData, persist bool) (err error) {
	localStorage := GetLocalStorage()
	legacyRaw, ok := localStorage[legacyAIEditorActionsStorageKey]
	if !ok {
		return nil
	}

	var legacyActions []*legacyAIEditorAction
	if legacyJSON, isString := legacyRaw.(string); isString {
		if err = gulu.JSON.UnmarshalJSON([]byte(legacyJSON), &legacyActions); err != nil {
			return fmt.Errorf("unmarshal legacy AI editor actions failed: %w", err)
		}
	} else {
		legacyJSON, marshalErr := gulu.JSON.MarshalJSON(legacyRaw)
		if marshalErr != nil {
			return fmt.Errorf("marshal legacy AI editor actions failed: %w", marshalErr)
		}
		if err = gulu.JSON.UnmarshalJSON(legacyJSON, &legacyActions); err != nil {
			return fmt.Errorf("unmarshal legacy AI editor actions failed: %w", err)
		}
	}

	type actionKey struct {
		name   string
		action string
	}
	existing := make(map[actionKey]struct{}, len(data.Actions))
	for _, action := range data.Actions {
		existing[actionKey{name: action.Name, action: action.Action}] = struct{}{}

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Inspect data/storage/local.json and validate the value of key "local-ai" with any JSON parser
  2. If it holds a string, decode/escape it and confirm it parses as [{"name":...,"memo":...}]
  3. Repair the JSON in place, or delete the local-ai key (old custom AI actions are discarded; the migration then no-ops)
  4. Back up local.json before editing, with SiYuan closed

Example fix

// before: corrupted value in data/storage/local.json
"local-ai": "[{name: 'translate', memo: 'Translate'}]"  // single quotes, unquoted keys
// after: strict JSON, memo field
"local-ai": "[{\"name\":\"translate\",\"memo\":\"Translate the current block\"}]"
Defensive patterns

Strategy: validation

Validate before calling

// Validate the legacy value before any AI editor action call triggers migration
ls := model.GetLocalStorage()
if raw, ok := ls["local-ai"]; ok {
    if s, isStr := raw.(string); isStr {
        var probe []*struct{ Name, Memo string }
        if json.Unmarshal([]byte(s), &probe) != nil {
            // repair or drop the key before proceeding
        }
    }
}

Type guard

func isMigratableLegacyActions(raw any) bool {
    s, ok := raw.(string)
    if !ok { return false }
    var probe []struct {
        Name string `json:"name"`
        Memo string `json:"memo"`
    }
    return json.Unmarshal([]byte(s), &probe) == nil
}

Try / catch

if _, err := model.GetAIEditorActions(); err != nil && strings.Contains(err.Error(), "legacy AI editor actions") {
    // stop calling in a loop; fix data/storage/local.json "local-ai" first, then retry once
}

Prevention

When it happens

Trigger: A pre-migration frontend stored custom AI actions as a string under local-ai in data/storage/local.json, and that string is truncated, hand-edited, corrupted by sync merge conflicts, or contains a different JSON shape (e.g. an object instead of an array of {name,memo}).

Common situations: Manual editing of storage/local.json, sync-tool merge conflicts producing half-written JSON, older SiYuan versions writing an incompatible local-ai schema, or crash during a previous localStorage write.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/cae76dfdae294da5. Report an issue: GitHub.