Skip to content

fix(channels): most channels missing namedAccountPromotionKeys — multi-account promotion strips shared defaults #62387

@alexey-pelykh

Description

@alexey-pelykh

Summary

When a channel config has named accounts but no default, the doctor/channels add migration moves top-level account-scoped keys into accounts.default. For channels that declare namedAccountPromotionKeys, this is limited to credentials only — policy keys stay at the channel top level as shared defaults.

Problem: Most channels do NOT declare namedAccountPromotionKeys. The guard in resolveSingleAccountKeysToMove() short-circuits when the value is undefined:

if (
  hasNamedAccounts &&
  namedAccountPromotionKeys &&  // undefined for most channels → guard skipped
  !namedAccountPromotionKeys.includes(key)
) {
  return false;
}

This means ALL movable keys (groupPolicy, allowFrom, defaultTo, credentials, etc.) get swept into accounts.default, stripping shared defaults from existing named accounts.

Channels with the guard

Channel Source Status
Telegram BUNDLED_NAMED_ACCOUNT_PROMOTION_FALLBACKS ✅ Patched
Matrix Plugin setup surface (namedAccountPromotionKeys) ✅ Patched

Channels WITHOUT the guard (affected)

Slack, Discord, WhatsApp, Signal, IRC, MS Teams, Google Chat, iMessage, Line, Nostr, Mattermost, Synology Chat, BlueBubbles, Tlon, Twitch, Zalo, and all other extensions.

Repro

Before migration — top-level acts as inherited base for all accounts:

channels:
  slack:
    groupPolicy: "open"
    allowFrom: ["U123"]
    accounts:
      work:
        botToken: "xoxb-work"
      personal:
        botToken: "xoxb-personal"

Both work and personal inherit groupPolicy and allowFrom via the { ...base, ...account } merge in mergeSlackAccountConfig().

After doctor --fix — shared defaults moved into phantom default:

channels:
  slack:
    accounts:
      default:
        groupPolicy: "open"
        allowFrom: ["U123"]
      work:
        botToken: "xoxb-work"     # lost groupPolicy + allowFrom
      personal:
        botToken: "xoxb-personal" # lost groupPolicy + allowFrom

work and personal no longer inherit those values.

Expected behavior

Each channel should declare namedAccountPromotionKeys listing only credential/auth keys that are per-account by nature (e.g., botToken, appToken, signingSecret). Policy keys (groupPolicy, allowFrom, defaultTo) should remain at the channel top level as shared defaults.

Alternatively, the fallback when namedAccountPromotionKeys is undefined could default to credentials-only (conservative) rather than move-everything (destructive).

Relevant code

  • src/channels/plugins/setup-helpers.tsresolveSingleAccountKeysToMove()
  • src/commands/doctor/shared/channel-legacy-config-migrate.ts (calls via seedMissingDefaultAccountsFromSingleAccountBase)
  • BUNDLED_NAMED_ACCOUNT_PROMOTION_FALLBACKS — only has Telegram
  • BUNDLED_SINGLE_ACCOUNT_PROMOTION_FALLBACKS — only has Telegram

Related PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions