sipeed/picoclaw · error

failed to marshal security config: %w

Error message

failed to marshal security config: %w

What it means

Returned by saveSecurityConfig when yaml.Encoder fails to serialize the Config being written to security.yml. Encoding errors mean some value in the in-memory Config cannot be represented as YAML: a field whose custom MarshalYAML returns an error, a map key type the encoder rejects, or values like NaN that YAML cannot serialize. The write is aborted before WriteFileAtomic, so the existing file on disk is untouched.

Source

Thrown at pkg/config/security.go:200

		for key, value := range legacyRegistry.Param {
			if _, exists := registryCfg.Param[key]; !exists {
				registryCfg.Param[key] = value
			}
		}
		cfg.Tools.Skills.Registries.Set(name, registryCfg)
	}

	return nil
}

// saveSecurityConfig saves the security configuration to security.yml
func saveSecurityConfig(securityPath string, sec *Config) error {
	var buf bytes.Buffer
	enc := yaml.NewEncoder(&buf)
	enc.SetIndent(2)
	err := enc.Encode(sec)
	if err != nil {
		return fmt.Errorf("failed to marshal security config: %w", err)
	}
	return fileutil.WriteFileAtomic(securityPath, buf.Bytes(), 0o600)
}

// SensitiveDataCache caches the strings.Replacer for filtering sensitive data.
// Computed once on first access via sync.Once.
type SensitiveDataCache struct {
	replacer *strings.Replacer
	once     sync.Once
}

// SensitiveDataReplacer returns the strings.Replacer for filtering sensitive data.
// It is computed once on first access via sync.Once.
func (sec *Config) SensitiveDataReplacer() *strings.Replacer {
	sec.initSensitiveCache()
	return sec.sensitiveCache.replacer
}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Log the wrapped error — the yaml encoder names the value/type it failed on; fix or remove that value before saving
  2. If you built/modified Config in code, sanitize untrusted fields (drop unknown types, replace NaN with a valid number) before invoking save
  3. Reproduce with a minimal Config to isolate which field fails marshal, then correct its type
  4. Report/patch the custom MarshalYAML involved if a legit value cannot round-trip
Defensive patterns

Strategy: try-catch

Validate before calling

// Dry-run marshal before saving to catch unserializable values.
func canMarshal(v any) error {
	enc := yaml.NewEncoder(io.Discard)
	defer enc.Close()
	return enc.Encode(v)
}

Try / catch

if err := saveSecurityConfig(path, sec); err != nil {
	// existing file is untouched (atomic write) — safe to log, fix Config, retry
	log.Printf("security save failed (file unchanged): %v", err)
	return err
}

Prevention

When it happens

Trigger: Calling the save path (config rotation, credential update) while Config holds an unmarshalable value — e.g. a Channels entry or model_list element of a type whose marshaler errors, a map with non-string keys injected programmatically, or float NaN in a numeric field. enc.Encode(sec) returns the marshal error and it is wrapped here.

Common situations: Programmatic mutation of Config before save (plugins/tests inserting odd types), version changes that added a field with a buggy custom marshaler, or secrets containing values that fail a strict marshaler. Because the write is atomic, callers see the old security.yml preserved and only the error to diagnose.

Related errors


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