sipeed/picoclaw · error

channel %q: expected %T settings, got %T

Error message

channel %q: expected %T settings, got %T

What it means

After a successful decode, RegisterSafeFactory type-asserts the decoded settings to the factory's generic parameter S (*config.TelegramSettings, *config.SlackSettings, ...). If the decoded value is a different settings struct, the assertion fails and reports both the expected and actual types. This almost always means the channel's declared type and the settings stored under that channel name disagree.

Source

Thrown at pkg/channels/registry.go:55

//	            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.
func GetRegisteredFactoryNames() []string {
	factoriesMu.RLock()
	defer factoriesMu.RUnlock()
	names := make([]string, 0, len(factories))

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Align the stanza: the channel's type field and its settings body must both belong to the same channel type — replace the settings block with the correct shape for the registered factory.
  2. If you renamed or re-typed a channel, create a fresh stanza instead of mutating the old one, so old settings cannot leak in.
  3. Check the error text itself: 'expected *config.XSettings, got *config.YSettings' tells you exactly which two types collided; rename the mismatched stanza.
  4. When registering custom channels, use a unique channelType string that does not collide with built-ins.

Example fix

# before: type says slack, settings are telegram-shaped
channels:
  my-bot:
    type: slack
    settings:
      api_token: "123:ABC"      # telegram field
      allowed_chats: ["-100123"] # telegram field

# after: settings match the declared type
channels:
  my-bot:
    type: slack
    settings:
      bot_token: "xoxb-..."
      app_token: "xapp-..."
Defensive patterns

Strategy: type-guard

Validate before calling

// before constructing, check the settings decode to the expected type for this channel type
settings, err := bc.GetDecoded()
if err != nil { return err }
if _, ok := settings.(*config.SlackSettings); !ok { // expected type for this factory
    return fmt.Errorf("settings type %T does not match this channel type", settings)
}

Type guard

// generic guard mirroring RegisterSafeFactory
func settingsOfType[S any](bc *config.Channel) (*S, bool) {
    decoded, err := bc.GetDecoded()
    if err != nil { return nil, false }
    s, ok := decoded.(*S)
    return s, ok
}

Try / catch

// Go: if err contains "expected *config." -> the error message itself names both types; fix the stanza's type-vs-settings mismatch, do not retry

Prevention

When it happens

Trigger: A channels.<name> stanza whose type field maps to channel type X (e.g. slack) but whose settings block decodes to another channel's struct (e.g. TelegramSettings) — typically because the stanza was registered with a different factory than the one that owns the settings; reusing an existing channel name for a new channel type without migrating settings; copy-pasting a channel block and changing type but not the settings shape.

Common situations: Cloning a telegram channel stanza to make a slack channel and forgetting to replace the settings body; changing a channel's type in place while the config file retains the old settings structure; version skew where a settings struct moved between packages; custom channels registering a factory under an already-registered type name.

Related errors


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