{"record":{"id":"4f7bf4a28289ee5c","repo":"sipeed/picoclaw","slug":"channel-q-expected-t-settings-got-t","errorCode":null,"errorMessage":"channel %q: expected %T settings, got %T","messagePattern":"channel %q: expected %T settings, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/registry.go","lineNumber":55,"sourceCode":"//\t            return NewTelegramChannel(bc, c, b)\n//\t        })\n//\t}\nfunc RegisterSafeFactory[S any](\n\tchannelType string,\n\tctor func(bc *config.Channel, settings *S, bus *bus.MessageBus) (Channel, error),\n) {\n\tRegisterFactory(channelType, func(channelName, _ string, cfg *config.Config, b *bus.MessageBus) (Channel, error) {\n\t\tbc := cfg.Channels[channelName]\n\t\tif bc == nil {\n\t\t\treturn nil, fmt.Errorf(\"channel %q: config not found\", channelName)\n\t\t}\n\t\tdecoded, err := bc.GetDecoded()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"channel %q: failed to decode settings: %w\", channelName, err)\n\t\t}\n\t\tsettings, ok := decoded.(*S)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"channel %q: expected %T settings, got %T\", channelName, (*S)(nil), decoded)\n\t\t}\n\t\treturn ctor(bc, settings, b)\n\t})\n}\n\n// getFactory looks up a channel factory by name.\nfunc getFactory(name string) (ChannelFactory, bool) {\n\tfactoriesMu.RLock()\n\tdefer factoriesMu.RUnlock()\n\tf, ok := factories[name]\n\treturn f, ok\n}\n\n// GetRegisteredFactoryNames returns a slice of all registered channel factory names.\nfunc GetRegisteredFactoryNames() []string {\n\tfactoriesMu.RLock()\n\tdefer factoriesMu.RUnlock()\n\tnames := make([]string, 0, len(factories))","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/registry.go#L37-L73","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: type says slack, settings are telegram-shaped\nchannels:\n  my-bot:\n    type: slack\n    settings:\n      api_token: \"123:ABC\"      # telegram field\n      allowed_chats: [\"-100123\"] # telegram field\n\n# after: settings match the declared type\nchannels:\n  my-bot:\n    type: slack\n    settings:\n      bot_token: \"xoxb-...\"\n      app_token: \"xapp-...\"","handlingStrategy":"type-guard","validationCode":"// before constructing, check the settings decode to the expected type for this channel type\nsettings, err := bc.GetDecoded()\nif err != nil { return err }\nif _, ok := settings.(*config.SlackSettings); !ok { // expected type for this factory\n    return fmt.Errorf(\"settings type %T does not match this channel type\", settings)\n}","typeGuard":"// generic guard mirroring RegisterSafeFactory\nfunc settingsOfType[S any](bc *config.Channel) (*S, bool) {\n    decoded, err := bc.GetDecoded()\n    if err != nil { return nil, false }\n    s, ok := decoded.(*S)\n    return s, ok\n}","tryCatchPattern":"// Go: if err contains \"expected *config.\" -> the error message itself names both types; fix the stanza's type-vs-settings mismatch, do not retry","preventionTips":["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"],"tags":["registry","config","type-mismatch","settings","startup"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}