Skip to content

resolveIrcHostFromConfig: account-specific host ignored when accountId === irc.defaultAccount #528

Description

@NOVA-Openclaw

Summary

resolveIrcHostFromConfig in memory/plugins/turn-context/src/entity-resolver.ts (added in #525, closing #522) skips the account-specific host lookup whenever the supplied accountId equals channels.irc.defaultAccount, falling back straight to the top-level channels.irc.host instead.

export function resolveIrcHostFromConfig(accountId?: string, config?: Record<string, unknown>): string | undefined {
  const cfg = (config ?? readOpenClawConfig()) as OpenClawConfigShape | null | undefined;
  const irc = cfg?.channels?.irc;
  if (!irc) return undefined;

  // If an explicit account id is provided, prefer its host, then fall back to top-level.
  if (accountId && accountId !== irc.defaultAccount) {
    const accountHost = irc.accounts?.[accountId]?.host?.trim();
    if (accountHost) return accountHost;
  }

  return irc.host?.trim();
}

Repro

Given:

{
  "channels": {
    "irc": {
      "host": "irc.stale-legacy.net",
      "defaultAccount": "primary",
      "accounts": {
        "primary": { "host": "irc.primary-correct.net" },
        "secondary": { "host": "irc.libera.chat" }
      }
    }
  }
}

resolveIrcHostFromConfig("primary", config) returns "irc.stale-legacy.net" (the stale top-level value) instead of "irc.primary-correct.net" (the account-specific value), because accountId ("primary") === irc.defaultAccount ("primary") short-circuits the account lookup entirely.

Verified locally against the PR #525 branch (commit 2a98674) with a standalone regression test — deepEqual assertion fails, returning the stale host.

Why this is wrong

This contradicts the account-merge precedence used everywhere else in the codebase for IRC config (resolveMergedAccountConfig / mergeAccountConfig in src/channels/plugins/account-helpers.ts, used by extensions/irc/src/accounts.ts): account-specific config always overrides channel-level defaults, unconditionally, regardless of whether the accountId happens to match defaultAccount. defaultAccount only determines which accountId is selected when the caller omits one — it should have no bearing on precedence once an accountId is already known.

Impact

  • Current production is not affected. The live channels.irc config has no accounts map and no defaultAccount set, so this branch cannot be reached today.
  • Future risk: any multi-account IRC setup that sets defaultAccount explicitly (matching one of the accounts keys) with a differing account-level host will silently resolve entity identities against the wrong network string, producing <wrong-network>/<nick> composite keys that never match production irc_username facts. Silent failure — no crash, just no entity resolution.

Suggested fix

Drop the accountId !== irc.defaultAccount guard entirely:

export function resolveIrcHostFromConfig(accountId?: string, config?: Record<string, unknown>): string | undefined {
  const cfg = (config ?? readOpenClawConfig()) as OpenClawConfigShape | null | undefined;
  const irc = cfg?.channels?.irc;
  if (!irc) return undefined;

  if (accountId) {
    const accountHost = irc.accounts?.[accountId]?.host?.trim();
    if (accountHost) return accountHost;
  }

  return irc.host?.trim();
}

Add a regression test asserting that an explicit defaultAccount matching the passed accountId still prefers accounts[accountId].host over a differing top-level host.

Origin

Found during Gem's step-6 re-verification desk review of PR #525 (SE Run #499). Filed as a non-blocking follow-up per DEFECT_MANAGEMENT triage (S3/P2 — no production impact today, narrow future-facing edge case, workaround is simply not setting defaultAccount + differing per-account host until fixed).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions