siyuan-note/siyuan · error

invalid config.%s: %v

Error message

invalid config.%s: %v

What it means

Thrown by unmarshalCapabilityJSON when the JSON-serialized effects or actionEffects value fails to unmarshal into the Go target type (tools.ToolEffects or map[string]tools.ToolEffects). The JSON is syntactically valid but structurally incompatible — wrong field types, unexpected nesting, or unknown required fields.

Source

Thrown at kernel/plugin/api_agent.go:305

	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. Match the effects object structure to the tools.ToolEffects Go struct definition
  2. Use plain objects with correct field types (string arrays for writes/reads, etc.)
  3. Check kernel/mcp/tools package for the current ToolEffects type definition

Example fix

// before
await siyuan.agent.registerCapability('myTool', {
  description: '...',
  inputSchema: { type: 'object' },
  effects: 'writes-blocks'  // string, not object
}, handler);

// after
await siyuan.agent.registerCapability('myTool', {
  description: '...',
  inputSchema: { type: 'object' },
  effects: { writes: ['blocks'] }
}, handler);
Defensive patterns

Strategy: validation

Validate before calling

// Validate effects structure matches ToolEffects
function validateEffects(effects) {
  if (effects == null) return true;
  if (typeof effects !== 'object' || Array.isArray(effects)) {
    throw new Error('effects must be a plain object');
  }
  for (const key of Object.keys(effects)) {
    if (Array.isArray(effects[key])) {
      effects[key].forEach(v => {
        if (typeof v !== 'string') throw new Error(`effects.${key} must be a string array`);
      });
    }
  }
}
validateEffects(config.effects);
validateEffects(config.actionEffects);

Type guard

function isPlainStringRecord(v) {
  if (v == null) return true;
  if (typeof v !== 'object' || Array.isArray(v)) return false;
  return Object.values(v).every(val => Array.isArray(val) && val.every(x => typeof x === 'string'));
}

Prevention

When it happens

Trigger: Passing config.effects with incorrect structure — e.g., effects = 'read-only' (a string instead of an object), or effects = { writes: 'blocks' } (string instead of string array), where the Go struct expects specific field types.

Common situations: Plugin developer guesses the effects format instead of matching the tools.ToolEffects struct; a version change altered the ToolEffects struct shape; the effects object was built from untrusted external data.

Related errors


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