siyuan-note/siyuan · error

config.actionEffects contains an empty action

Error message

config.actionEffects contains an empty action

What it means

Thrown by jsCapabilityActionEffectsToGoEffects after successfully unmarshaling the actionEffects map — one or more keys (action names) in the map are empty or whitespace-only after strings.TrimSpace. Action names are used as identifiers in the declared effects map, so empty keys would be ambiguous.

Source

Thrown at kernel/plugin/api_agent.go:293

	return
}

func jsCapabilityEffectsToGoEffects(rt *goja.Runtime, value goja.Value) (*tools.ToolEffects, error) {
	effects := &tools.ToolEffects{}
	if err := unmarshalCapabilityJSON(rt, value, effects, "effects"); err != nil {
		return nil, err
	}
	return effects, nil
}

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. Remove empty or whitespace-only keys from the actionEffects map before passing it
  2. Validate action names are non-empty when constructing the actionEffects object

Example fix

// before
const actionEffects = {
  '': { writes: ['blocks'] },
  'create': { writes: ['blocks'] }
};
await siyuan.agent.registerCapability('myTool', {
  description: '...',
  inputSchema: { type: 'object' },
  actionEffects
}, handler);

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

Strategy: validation

Validate before calling

// Remove empty action keys before passing
if (config.actionEffects) {
  for (const key of Object.keys(config.actionEffects)) {
    if (key.trim() === '') {
      delete config.actionEffects[key];
    }
  }
}

Type guard

function hasNoEmptyActionKeys(actionEffects) {
  if (!actionEffects) return true;
  return Object.keys(actionEffects).every(k => k.trim().length > 0);
}

Prevention

When it happens

Trigger: Passing config.actionEffects = { '': {...} } or config.actionEffects = { ' ': {...} } — a map with at least one empty/whitespace key. This can happen when action names are dynamically generated from data and one source value is empty.

Common situations: Plugin generates action names from user input or document data that may be empty; a default empty-string key was accidentally included; the actionEffects map was built from a list that contained an empty element.

Related errors


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