sipeed/picoclaw · error

channel %q: config not found

Error message

channel %q: config not found

What it means

RegisterSafeFactory installs a type-safe wrapper around per-channel constructors; when the manager looks up cfg.Channels[channelName] to build a channel, a missing map entry yields this error. It means the registry was asked to construct a channel whose name has no corresponding stanza under channels in the loaded config.Config — the factory ran, but there is nothing to decode settings from.

Source

Thrown at pkg/channels/registry.go:47

// RegisterSafeFactory is a convenience wrapper that handles GetDecoded() error checking
// and type assertion, reducing boilerplate in channel init() functions.
//
// Usage:
//
//	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]

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Add a channels.<channelName> stanza with the correct key exactly matching the requested name (keys are case-sensitive).
  2. Fix the mismatch: whichever side names the channel (enabled list, manager, CLI) must equal the map key in cfg.Channels.
  3. Validate config at startup — iterate the names you intend to enable and assert each has a cfg.Channels entry before constructing channels.
  4. Check YAML indentation so each channel sits directly under channels:.

Example fix

# before: enabled name has no matching stanza
channels:
  telegram-main:
    type: telegram
    ...
# manager asked to build "telegram"

# after: names match exactly
channels:
  telegram:
    type: telegram
    ...
Defensive patterns

Strategy: validation

Validate before calling

// before building channels, assert every requested name exists in config
for _, name := range wantedChannels {
    if cfg.Channels[name] == nil {
        return fmt.Errorf("channel %q missing from config.Channels — add a stanza or fix the name", name)
    }
}

Type guard

// guard the lookup itself
func hasChannelConfig(cfg *config.Config, name string) bool {
    _, ok := cfg.Channels[name]
    return ok
}

Try / catch

// Go: if err from Build/Create contains "config not found", stop and fix config; retrying cannot help

Prevention

When it happens

Trigger: RegisterFactory/RegisterSafeFactory invoked for a channelName that is not a key in cfg.Channels (e.g. "telegram-bot" configured in an enabled-list but absent as a channels stanza); typo/mismatch between the enabled channel name and the YAML key; config loaded from a different file than expected so the stanza never existed.

Common situations: Renaming a channel in one place (enabled list, CLI flag) but not in the channels block; YAML indentation putting the channel stanza under the wrong parent; multiple config sources (file + env overrides) where the file providing the stanza fails to load; channel name case mismatch ("Slack" vs "slack").

Related errors


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