sipeed/picoclaw · error

failed to parse security config %s: %w

Error message

failed to parse security config %s: %w

What it means

Returned by loadSecurityConfig when the second yaml.Unmarshal — decoding security.yml directly into the Config struct — fails. The file is valid YAML as a node tree (that was checked first), but its contents do not fit the Config schema: wrong field types, custom UnmarshalYAML methods rejecting values, or duplicate/malformed keys. The message includes the security.yml path plus the wrapped decoder error with field context.

Source

Thrown at pkg/config/security.go:78

	// Extract channels node (support both 'channels' and 'channel_list' keys)
	var channelsNode *yaml.Node
	if len(rootNode.Content) > 0 {
		content := rootNode.Content[0].Content
		for i := 0; i < len(content); i += 2 {
			if i+1 < len(content) {
				key := content[i].Value
				if key == "channels" || key == "channel_list" {
					channelsNode = content[i+1]
					break
				}
			}
		}
	}

	// Unmarshal non-channel fields from security.yml
	// This will resolve encrypted values for model_list, tools, etc.
	if err := yaml.Unmarshal(data, cfg); err != nil {
		return fmt.Errorf("failed to parse security config %s: %w", securityPath, err)
	}
	if err := applyLegacySkillsSecurityConfig(cfg, data); err != nil {
		return fmt.Errorf("failed to parse legacy skills security config: %w", err)
	}

	// Restore channels from saved, then manually merge from security.yml
	cfg.Channels = make(ChannelsConfig)
	for name, savedBC := range savedChannels {
		cfg.Channels[name] = savedBC
	}

	// If we found a channels node in security.yml, merge it into existing channels
	if channelsNode != nil {
		if err := cfg.Channels.UnmarshalYAML(channelsNode); err != nil {
			return fmt.Errorf("failed to merge channels from security config: %w", err)
		}
	}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Read the wrapped error: it names the failing field/type (e.g. `cannot unmarshal !!str into int`); fix that exact value in security.yml
  2. Compare your security.yml keys against the current Config struct/version's documented schema — usually a version-upgrade mismatch
  3. Re-generate security.yml with the current version (save/rotate flow) and re-apply only the values you need
  4. If migrating from an older release, run the provided migration path instead of manually copying the old file

Example fix

# before
provider:
  timeout: "30s-not-a-number-field"

# after
provider:
  timeout: 30
Defensive patterns

Strategy: validation

Validate before calling

// Strict-type decode into the same Config before the real load.
func strictCheck(data []byte, cfg *Config) error {
	dec := yaml.NewDecoder(bytes.NewReader(data))
	dec.KnownFields(true) // catch unknown keys too
	var probe Config
	return dec.Decode(&probe)
}

Try / catch

if err := loadSecurityConfig(cfg, p); err != nil {
	var typeErr *yaml.TypeError
	if errors.As(err, &typeErr) {
		for _, e := range typeErr.Errors { log.Printf("field error: %s", e) }
	}
	return err
}

Prevention

When it happens

Trigger: A field that must be a scalar holds a map (e.g. `passphrase: {a: 1}`), a numeric field holds a non-number, or a nested type's custom UnmarshalYAML (e.g. ChannelsConfig, model_list entries) rejects a value. yaml.TypeError from the struct decode is wrapped into this message.

Common situations: Schema drift between an old security.yml and a new binary (fields changed shape across versions), hand-editing that moves a key one level up/down, or encrypted/legacy values placed under the wrong section. The specific field is named in the wrapped error text.

Understand the failure class

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/9a9358e70396b84c. Report an issue: GitHub.