siyuan-note/siyuan · error

failed to serialize config.%s: %v

Error message

failed to serialize config.%s: %v

What it means

Thrown by unmarshalCapabilityJSON when MarshalJSON fails on the JS value representing config.effects or config.actionEffects. This is the same serialization failure as error 974 but for the effects/actionEffects fields — the value contains non-serializable content like functions or circular references.

Source

Thrown at kernel/plugin/api_agent.go:302

}

func jsCapabilityActionEffectsToGoEffects(rt *goja.Runtime, value goja.Value) (map[string]tools.ToolEffects, error) {
	actionEffects := map[string]tools.ToolEffects{}
	if err := unmarshalCapabilityJSON(rt, value, &actionEffects, "actionEffects"); err != nil {
		return nil, err
	}
	for action := range actionEffects {
		if strings.TrimSpace(action) == "" {
			return nil, fmt.Errorf("config.actionEffects contains an empty action")
		}
	}
	return actionEffects, nil
}

func unmarshalCapabilityJSON(rt *goja.Runtime, value goja.Value, target any, field string) error {
	jsonValue, err := value.ToObject(rt).MarshalJSON()
	if err != nil {
		return fmt.Errorf("failed to serialize config.%s: %v", field, err)
	}
	if err = json.Unmarshal(jsonValue, target); err != nil {
		return fmt.Errorf("invalid config.%s: %v", field, err)
	}
	return nil
}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Ensure the effects/actionEffects value is a plain JSON-serializable object
  2. Strip non-serializable properties with JSON.parse(JSON.stringify(value)) before passing
  3. Construct the effects object from plain data, not from class instances with methods

Example fix

// before
const effects = {
  writes: ['blocks'],
  validate: () => true  // function — not serializable
};
await siyuan.agent.registerCapability('myTool', {
  description: '...',
  inputSchema: { type: 'object' },
  effects
}, handler);

// after
const effects = {
  writes: ['blocks']
};
Defensive patterns

Strategy: validation

Validate before calling

// Strip non-serializable properties from effects
if (config.effects) {
  config.effects = JSON.parse(JSON.stringify(config.effects));
}
if (config.actionEffects) {
  config.actionEffects = JSON.parse(JSON.stringify(config.actionEffects));
}

Type guard

function isJsonSerializable(obj) {
  try {
    JSON.stringify(obj);
    return true;
  } catch {
    return false;
  }
}

Try / catch

try {
  await siyuan.agent.registerCapability(name, config, handler);
} catch (e) {
  if (e.message.includes('serialize config.')) {
    if (config.effects) config.effects = JSON.parse(JSON.stringify(config.effects));
    if (config.actionEffects) config.actionEffects = JSON.parse(JSON.stringify(config.actionEffects));
  }
}

Prevention

When it happens

Trigger: Passing config.effects or config.actionEffects that contains function values, circular references, or symbols. For example, effects = { writes: ['blocks'], compute: () => [] } where 'compute' cannot be JSON-serialized.

Common situations: Plugin includes methods or getters on the effects object; the effects object shares references creating cycles; a builder pattern left intermediate state on the object.

Related errors


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