Skip to content

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

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

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:data-lossCan lose, corrupt, or silently drop user/session/config data.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions