sipeed/picoclaw · error

channel %q: failed to decode settings: %w

Error message

channel %q: failed to decode settings: %w

What it means

Inside the RegisterSafeFactory wrapper, bc.GetDecoded() parses/decrypts a channel's settings block into a concrete settings struct; failure is wrapped as "channel %q: failed to decode settings". The underlying cause is preserved with %w and is typically malformed settings content, a wrong shape for the target struct, or a secret that cannot be decrypted with the current key.

Source

Thrown at pkg/channels/registry.go:51

//
//	func init() {
//	    channels.RegisterSafeFactory(config.ChannelTelegram,
//	        func(bc *config.Channel, c *config.TelegramSettings, b *bus.MessageBus) (channels.Channel, error) {
//	            return NewTelegramChannel(bc, c, b)
//	        })
//	}
func RegisterSafeFactory[S any](
	channelType string,
	ctor func(bc *config.Channel, settings *S, bus *bus.MessageBus) (Channel, error),
) {
	RegisterFactory(channelType, func(channelName, _ string, cfg *config.Config, b *bus.MessageBus) (Channel, error) {
		bc := cfg.Channels[channelName]
		if bc == nil {
			return nil, fmt.Errorf("channel %q: config not found", channelName)
		}
		decoded, err := bc.GetDecoded()
		if err != nil {
			return nil, fmt.Errorf("channel %q: failed to decode settings: %w", channelName, err)
		}
		settings, ok := decoded.(*S)
		if !ok {
			return nil, fmt.Errorf("channel %q: expected %T settings, got %T", channelName, (*S)(nil), decoded)
		}
		return ctor(bc, settings, b)
	})
}

// getFactory looks up a channel factory by name.
func getFactory(name string) (ChannelFactory, bool) {
	factoriesMu.RLock()
	defer factoriesMu.RUnlock()
	f, ok := factories[name]
	return f, ok
}

// GetRegisteredFactoryNames returns a slice of all registered channel factory names.

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Unwrap the error (errors.Unwrap / %v) — decode libraries name the exact field and reason; fix that field in the channels.<name> stanza.
  2. If settings are encrypted/secret-protected, ensure the decryption key/environment (devkey/secret registry) is the one that encrypted them, or re-enter the settings in plaintext and re-encrypt.
  3. Run a config-validate pass or load the config in a test harness to catch decode errors before channel startup.
  4. After upgrades, diff your settings block against the current struct definition for that channel type.

Example fix

// before: shipping config errors to users
ch, err := channels.Build(name, cfg, bus)
if err != nil { return err }

// after: log the decode cause with the channel name
if err != nil {
    var decodeErr *json.UnmarshalTypeError // or yaml error via errors.As
    if errors.As(err, &decodeErr) {
        log.Printf("config error in channels.%s field %s: %v", name, decodeErr.Field, err)
    }
    return err
}
Defensive patterns

Strategy: validation

Validate before calling

// dry-run decode during config validation
for name, bc := range cfg.Channels {
    if _, err := bc.GetDecoded(); err != nil {
        return fmt.Errorf("channel %s settings invalid: %w", name, err)
    }
}

Try / catch

// if err != nil && strings.Contains(err.Error(), "failed to decode settings") -> unwrap (errors.Unwrap) to get field-level cause; fail startup with the channel name in the message

Prevention

When it happens

Trigger: Settings YAML/JSON for the channel is syntactically broken or has wrong field types (e.g. webhooks: "string" where a map is expected); settings stored encrypted but the decryption key changed or is missing; env-var placeholders in settings that expand to invalid values; schema drift after an upgrade moved fields.

Common situations: Upgrading the app to a version with a new settings schema while old config files keep removed fields with wrong types; moving an encrypted config file between hosts with different secret keys; hand-editing a locked/encrypted channel block; secrets referenced by name that no longer resolve.

Understand the failure class

Related errors


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