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
- 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.
- If you renamed or re-typed a channel, create a fresh stanza instead of mutating the old one, so old settings cannot leak in.
- Check the error text itself: 'expected *config.XSettings, got *config.YSettings' tells you exactly which two types collided; rename the mismatched stanza.
- 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 cloning a channel stanza, replace BOTH the type and the whole settings body
- Never register a custom factory under a built-in channel type name
- Read the 'expected X, got Y' text — it identifies the exact collision
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
- channel %q: failed to decode settings: %w
- channel %q: config not found
- must be in range 1-65535
- ✗ registry '%s' not found or not enabled. check your config
- failed to load MCP servers: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/4f7bf4a28289ee5c.
Report an issue: GitHub.