Skip to content

fix(irc): scope server/port/nickname/channel/use_tls reads to the active profile under multiplexing - #100640

Closed
nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/irc-multiplex-config-scope
Closed

nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/irc-multiplex-config-scope

Conversation

@nftpoetrist

Copy link
Copy Markdown
Contributor

Summary

  • IRCAdapter.__init__, check_requirements, validate_config, is_connected, _env_enablement, and _standalone_send all read IRC_SERVER/IRC_PORT/IRC_NICKNAME/IRC_CHANNEL/IRC_USE_TLS via raw os.getenv() — only IRC_SERVER_PASSWORD/IRC_NICKSERV_PASSWORD were already routed through the module's _get_scoped_secret() helper.
  • Under gateway.multiplex_profiles, env_enablement_fn/check_fn/is_connected all run inside the same registry-enablement loop in load_gateway_config() (gateway/config.py, ~lines 2704-2820) which runs inside _profile_runtime_scope for secondary profiles, and adapter construction runs scoped the same way — so os.environ there still holds the DEFAULT profile's env-bridge output.
  • Notably a stronger variant than the sibling fixes in this series: __init__'s original os.getenv("IRC_SERVER") or extra.get(...) ordering let a raw env read override even an explicitly configured config.yaml extra. A secondary profile that set its own server/channel via extra would still silently connect to the default profile's IRC server/channel/nick, because the default profile's config is always bridged to os.environ under multiplex and env was checked first. (The LINE/DingTalk/Teams/SMS/WeCom/ntfy fixes in this series didn't have this extra layer of severity — those used extra.get(...) or os.getenv(...), so extra already won.)

Fix

Switch every raw IRC_* read (except the two secret-material fields already scoped) to the module's existing _get_scoped_secret() helper — same fallback semantics as the rest of the series (scoped miss returns the default, never cross-profile-borrows os.environ; the DEFAULT profile's own unscoped construction still falls back to os.environ). Also collapses a double os.getenv("IRC_USE_TLS") call in __init__ into a single _get_scoped_secret() lookup (identical behavior, one scope read instead of two).

Tests

Added TestMultiplexProfileScope (6 tests) to tests/gateway/test_irc_adapter.py, mirroring the fixture/assertion style already established in tests/gateway/test_line_plugin.py's class of the same name.

Mutation-verified: stashed the production fix and confirmed 5 of 6 new tests fail against pre-fix code — including the "extra wins" test, since IRC's original env-first ordering meant even an explicit extra config wasn't a safe boundary before the fix. The 1 passing test (test_default_profile_unscoped_keeps_env_precedence) is a non-differentiating regression guard that correctly passes either way. Restored the fix; all 23 tests in the file pass, plus the file's 5 parametrized _get_scoped_secret tests in tests/gateway/test_adapter_startup_secret_scope.py.

Competitor check

Searched IRC_SERVER, irc multiplex, irc scope. Three open PRs touch plugins/platforms/irc/adapter.py:

No open or merged PR touches the actual scope-leak fixed here.

Checklist

  • Read current adapter code directly (not from a stale scan summary)
  • Minimal, precedent-matching fix (reuses existing _get_scoped_secret helper)
  • New regression tests, mutation-verified against the pre-fix code
  • Full existing IRC test suite passes
  • Fresh competitor PR search immediately before opening this PR

…ive profile under multiplexing

IRCAdapter.__init__, check_requirements, validate_config, is_connected,
_env_enablement, and _standalone_send all read IRC_SERVER/IRC_PORT/
IRC_NICKNAME/IRC_CHANNEL/IRC_USE_TLS via raw os.getenv -- only
IRC_SERVER_PASSWORD/IRC_NICKSERV_PASSWORD already went through the
module's _get_scoped_secret helper. Under gateway.multiplex_profiles,
env_enablement_fn/check_fn/is_connected all run inside the registry-
enablement loop in load_gateway_config() (gateway/config.py, ~lines
2704-2820), scoped for secondary profiles via _profile_runtime_scope,
and adapter construction runs scoped the same way -- so os.environ there
still holds the DEFAULT profile's env-bridge output.

Notably, __init__'s original `os.getenv("IRC_SERVER") or extra.get(...)`
ordering let a raw env read override even an explicitly configured
config.yaml extra -- a secondary profile that set its own server/channel
via config.yaml extra would still silently connect to the default
profile's IRC server/channel/nick if the default profile bridged its own
config to env (which it always does under multiplex). This is a stronger
variant of the same bug fixed for the sibling LINE/DingTalk/Teams/SMS/
WeCom/ntfy adapters in this series -- there, extra already won because
of the `extra.get(...) or os.getenv(...)` order.

Switch every raw IRC_* read (except IRC_SERVER_PASSWORD/
IRC_NICKSERV_PASSWORD, already scoped) to _get_scoped_secret(), matching
the module's existing helper. Also collapses a double os.getenv("IRC_USE_TLS")
read in __init__ into a single _get_scoped_secret() call (same behavior,
one scope lookup instead of two).

Adds a new TestMultiplexProfileScope class to tests/gateway/test_irc_adapter.py
(6 tests) mirroring the fixture/assertion style established in
tests/gateway/test_line_plugin.py's TestMultiplexProfileScope. Mutation-
verified: stashed the production fix and confirmed 5 of 6 new tests fail
against pre-fix code -- including the "extra wins" test, since IRC's
original env-first ordering meant even an explicit extra config was not
a safe differentiator boundary before the fix (only the DEFAULT-profile-
unscoped-precedence test is a non-differentiating regression guard that
correctly passes either way). Restored the fix; all 23 tests in the file
pass, plus the file's 5 parametrized _get_scoped_secret tests in
test_adapter_startup_secret_scope.py.
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins area/profiles Multi-profile isolation, HERMES_HOME scoping labels Sep 1, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference; please use your judgment.

Consistent scope-aware fix for the IRC adapter: all IRC_* reads now route through _get_scoped_secret, closing the multiplex leak where a secondary profile would connect to the default profile's server/channel (and inherit its cron home_channel). Unscoped behavior is unchanged.

  • Good refactor: IRC_USE_TLS is read once into _use_tls_raw (adapter.py:139) instead of calling os.getenv twice, avoiding a subtle split where the two calls could differ.
  • In __init__, the TLS ternary is guarded by truthiness (adapter.py:141), so None/empty falls through to extra — correct.
  • check_requirements reads scoped env only (no config path), which matches its pre-existing semantics; just confirm the requirements-check callers run unscoped or accept the fail-closed result for scoped profiles.
  • _env_enablement correctly returns None when a scoped profile lacks its own server/channel, preventing auto-enable from the default profile's env.
  • Tests cover extra-wins, fail-closed defaults (port 6697, nickname hermes-bot, tls True), and unscoped precedence.

@teknium1

teknium1 commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Thanks @nftpoetrist — Merged via #101252 (2e25b47) on current main.

Your commits from this PR were cherry-picked onto the salvage branch with your git authorship preserved, so the credit is yours in git log.

Closing this PR since the work is now on main.

@teknium1 teknium1 closed this Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants