Skip to content

fix(matrix): scope require_mention/process_notices/session_scope/auto_thread/dm_mention_threads/max_message_length to the active profile under multiplexing - #102097

Closed
nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/matrix-multiplex-apply-yaml-config
Closed

nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/matrix-multiplex-apply-yaml-config

Conversation

@nftpoetrist

Copy link
Copy Markdown
Contributor

Problem

plugins/platforms/matrix/adapter.py only got a partial version of the same-day multiplex-scoping treatment applied to Mattermost/DingTalk/IRC/WhatsApp today. Its E2EE crypto-store path and MATRIX_RECOVERY_KEY read were already fixed (#69090), but several other settings were not:

  • require_mention, thread_require_mention, max_message_length already checked config.extra first, but fell back to raw os.getenv when extra was absent.
  • process_notices, session_scope, auto_thread, dm_mention_threads never consulted config.extra at all — raw os.getenv only.
  • allow_room_mentions, dm_auto_thread have no config.yaml path at all (env-only knobs) — raw os.getenv only.
  • _apply_yaml_config wrote MATRIX_REQUIRE_MENTION / MATRIX_PROCESS_NOTICES / MATRIX_SESSION_SCOPE / MATRIX_AUTO_THREAD / MATRIX_DM_MENTION_THREADS / MATRIX_MAX_MESSAGE_LENGTH into the process-global os.environ unconditionally (guarded only by not os.getenv(...), first-writer-wins) — the same apply_yaml_config_fn bug class already fixed for Mattermost/DingTalk/WhatsApp in this series.

Under gateway.multiplex_profiles, os.environ holds the default profile's YAML→env bridge output. A secondary profile with its own (or no) Matrix config could silently have its mention-gating, notice-processing, session-scope, auto-threading, and outbound chunk-size decisions driven by the default profile's settings for the adapter's entire runtime lifetime.

Fix

Mirrors the Mattermost/DingTalk apply_yaml_config_fn pattern (see 56d869d):

  • Add _profile_scoped_config_load() (same helper/name as Mattermost/DingTalk) — true when running inside a multiplexed secondary profile's scope.
  • Rewrite _apply_yaml_config to skip the env-bridge write for require_mention / process_notices / session_scope / auto_thread / dm_mention_threads / max_message_length under a scoped secondary profile, returning those values as a dict merged into that profile's own PlatformConfig.extra instead (the generic apply_yaml_config_fn dispatcher in gateway/config.py already does extra.update(seeded)).
  • Give _startup_env_secret() — Matrix's existing scoped-secret helper, previously used only for MATRIX_ACCESS_TOKEN/MATRIX_PASSWORD/MATRIX_HOMESERVER — an optional default parameter so non-credential settings can reuse it too (backward compatible; existing 1-arg call sites unaffected).
  • Switch every affected read site to read config.extra first, falling back to _startup_env_secret() instead of raw os.getenv: _parse_require_mention, _parse_thread_require_mention, _resolve_max_message_length, and (newly extra-aware) process_notices, session_scope, auto_thread, dm_mention_threads in __init__.
  • MATRIX_ALLOW_ROOM_MENTIONS/MATRIX_DM_AUTO_THREAD have no config.yaml path at all; scope the read itself via _startup_env_secret rather than inventing a new YAML bridge for them.

Explicitly out of scope

allowed_users/allowed_rooms/free_response_rooms/ignore_user_patterns/GATEWAY_ALLOW_ALL_USERS are not touched here. I checked open PRs first:

  • fix(gateway): read adapter allowlists through profile secret scope #88559 ("read adapter allowlists through profile secret scope", open) rewrites the same _apply_yaml_config function and independently defines the same-named _profile_scoped_config_load() helper — but exclusively for the allowlist fields above (_resolve_allowlist() in its diff). Its diff to plugins/platforms/matrix/adapter.py never touches require_mention/process_notices/session_scope/auto_thread/dm_mention_threads/max_message_length. Textual proximity (same function), zero semantic overlap with this PR — I verified via gh pr diff 88559.
  • fix(matrix): add missing dm_auto_thread config→env bridge #96039 ("add missing dm_auto_thread config→env bridge", open) is a 2-line feature-add to _apply_yaml_config's write side, giving dm_auto_thread a config.yaml path for the first time. It's complementary, not overlapping: this PR only scopes dm_auto_thread's read side (raw os.getenv_startup_env_secret) and deliberately does not add a YAML bridge for it, to avoid stepping on that PR's feature.

Both will need a routine rebase against this PR's _apply_yaml_config rewrite (same function, no semantic conflict).

Tests

Adds TestMultiplexProfileScope to tests/gateway/test_matrix.py (6 tests), mirroring the fixture/assertion style established in tests/gateway/test_irc_adapter.py's TestMultiplexProfileScope:

  • test_secondary_extra_wins_over_default_profile_env
  • test_secondary_missing_keys_fail_closed
  • test_secondary_own_env_only_scope_wins_over_default_profile_env
  • test_apply_yaml_config_scoped_skips_env_write_and_seeds_extra
  • test_apply_yaml_config_unscoped_default_profile_still_writes_env (non-differentiating regression guard)

Mutation-verified: reverted the production fix (kept the tests) and confirmed 4 of 5 differentiating tests fail against pre-fix code; the 5th (guarding that the default/unscoped profile keeps writing the env bridge) correctly passes either way. Restored the fix — all tests pass again.

Full results with the fix applied:

  • tests/gateway/test_matrix.py — 117 passed
  • tests/gateway/test_matrix_crypto_store_per_profile.py, test_matrix_recovery_key_scope.py, test_matrix_plugin_setup.py, test_matrix_mention.py, test_matrix_message_length.py — 28 passed
  • tests/gateway/test_matrix_dm_invite_recording.py, test_matrix_approval_reaction_fail_closed.py, test_matrix_exec_approval.py, test_matrix_voice.py, test_matrix_project_context_isolation.py, test_matrix_message_event_metadata.py — 18 passed, 1 skipped (pre-existing, unrelated)
  • tests/gateway/test_adapter_startup_secret_scope.py — 75 passed
  • tests/gateway/test_config.py — 63 passed
  • ruff check on both changed files — clean

…_thread/dm_mention_threads/max_message_length (and more) to the active profile under multiplexing

MatrixAdapter.__init__ read MATRIX_PROCESS_NOTICES/MATRIX_SESSION_SCOPE/
MATRIX_AUTO_THREAD/MATRIX_DM_MENTION_THREADS/MATRIX_ALLOW_ROOM_MENTIONS/
MATRIX_DM_AUTO_THREAD via raw os.getenv, never consulting config.extra.
require_mention/thread_require_mention/max_message_length already checked
config.extra first (via _parse_require_mention/_parse_thread_require_mention/
_resolve_max_message_length) but fell back to raw os.getenv when extra was
absent. _apply_yaml_config additionally wrote MATRIX_REQUIRE_MENTION/
MATRIX_PROCESS_NOTICES/MATRIX_SESSION_SCOPE/MATRIX_AUTO_THREAD/
MATRIX_DM_MENTION_THREADS/MATRIX_MAX_MESSAGE_LENGTH into the process-global
os.environ unconditionally (guarded only by `not os.getenv(...)`,
first-writer-wins) -- the same apply_yaml_config_fn bug class already fixed
for Mattermost/DingTalk/WhatsApp today. Matrix's E2EE crypto-store path and
recovery-key read were already fixed separately (NousResearch#69090); this closes the
remaining gap in the same file.

Under gateway.multiplex_profiles, os.environ holds the DEFAULT profile's
env-bridge output. A secondary profile with its own (different or absent)
Matrix config could silently have its mention-gating, notice-processing,
session-scope, auto-threading, and outbound chunk-size decisions driven by
the default profile's settings for the adapter's entire runtime lifetime.

Fix, mirroring the Mattermost/DingTalk apply_yaml_config_fn pattern:
- Add _profile_scoped_config_load() (same helper as Mattermost/DingTalk).
- Rewrite _apply_yaml_config to skip the env-bridge write for
  require_mention/process_notices/session_scope/auto_thread/
  dm_mention_threads/max_message_length under a multiplexed secondary
  profile's scope, returning those values as a dict merged into this
  profile's own PlatformConfig.extra instead. allowed_users/
  free_response_rooms/allowed_rooms/ignore_user_patterns are left on the
  legacy always-env-write path -- a separate, allowlist-focused PR (NousResearch#88559,
  open) covers those; verified its diff touches the same function but only
  those allowlist fields, no overlap with the fields fixed here.
- Give _startup_env_secret() (Matrix's existing scoped-secret helper,
  previously used only for MATRIX_ACCESS_TOKEN/PASSWORD/HOMESERVER) an
  optional `default` parameter so non-credential settings can reuse it
  too, and switch every affected read site (including the
  process_notices/session_scope/auto_thread/dm_mention_threads/
  allow_room_mentions/dm_auto_thread sites, which never consulted
  config.extra at all) to read extra first, falling back to the scoped
  helper instead of raw os.getenv.
- MATRIX_ALLOW_ROOM_MENTIONS/MATRIX_DM_AUTO_THREAD have no config.yaml
  path at all (env-only knobs); scope the read itself via
  _startup_env_secret rather than inventing a new YAML bridge for them
  (a separate open PR, NousResearch#96039, proposes adding a dm_auto_thread
  config-bridge as a feature -- complementary, not overlapping, since it
  only touches the write side and this fix only touches the read side).

Adds a TestMultiplexProfileScope class to tests/gateway/test_matrix.py (6
tests) mirroring the fixture/assertion style established in
tests/gateway/test_irc_adapter.py's TestMultiplexProfileScope. Mutation-
verified: reverted the production fix and confirmed 4 of 5 differentiating
tests fail against pre-fix code (the 5th, guarding that the default/
unscoped profile keeps writing the env bridge, correctly passes either way
as a non-differentiating regression guard). Restored the fix; all 117
tests in tests/gateway/test_matrix.py pass, plus
tests/gateway/test_matrix_crypto_store_per_profile.py,
tests/gateway/test_matrix_recovery_key_scope.py,
tests/gateway/test_matrix_plugin_setup.py,
tests/gateway/test_adapter_startup_secret_scope.py (75 tests), and
tests/gateway/test_config.py (63 tests).
@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins platform/matrix Matrix adapter (E2EE) area/config Config system, migrations, profiles area/profiles Multi-profile isolation, HERMES_HOME scoping sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Sep 3, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

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

PR #102097 — fix(matrix): scope require_mention/process_notices/session_scope to the active profile

  • plugins/platforms/matrix/adapter.py:982-995_startup_env_secret(name, default="") gains a default so non-credential settings keep their non-empty defaults through the same scoped read (scoped miss → default, no borrowing process env; only unscoped multiplex-default falls back to os.environ).
  • Read sites now check config.extra first with the scoped env as fallback: :572-578 (max_message_length), :1309-1342 (allow_room_mentions/auto_thread/dm_auto_thread/dm_mention_threads/session_scope/process_notices), :1439-1445 and :1460-1466 (require_mention parsers). Mirrors the Mattermost/DingTalk fix for this bug class.
  • :5405-5492_profile_scoped_config_load() discriminator (multiplex-active + secret scope installed, same as Buzz/Discord/Telegram/WhatsApp/LINE/DingTalk/Mattermost/IRC) plus _apply_yaml_config returning seeded extra instead of writing process-global env under secondary scope (env still wins over YAML single-profile via not os.getenv guards). Allowlist fields intentionally stay on the legacy path, flagged for the follow-up fix.
  • Tests tests/gateway/test_matrix.py:3343-3546 pin secondary-extra-wins, missing-keys-fail-closed, own-env-only scope, and scoped-vs-unscoped yaml application.

Non-blocking:

  • _reactions_enabled still reads raw os.getenv; presumably covered by the allowlist follow-up, but confirm it's on that list so it isn't orphaned between the two fixes.

Verdict: LGTM.

@teknium1

Copy link
Copy Markdown
Collaborator

Superseded by #108705 (on main as 199c66f). The Matrix reads you listed — require_mention, thread_require_mention, process_notices, session_scope, auto_thread, dm_mention_threads, max_message_length, plus the env-only allow_room_mentions/dm_auto_thread and e2ee_mode — now go through _get_scoped_secret (_env_truthy/_extra_truthy fall back to the scoped reader, not os.getenv), and _apply_yaml_config bridges through yaml_env_setter, which refuses to write under a secondary scope. Your branch had drifted off current main and carried more tests than we keep per fix, so it was re-done in that smaller shape rather than cherry-picked. Thanks, @nftpoetrist.

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

Labels

area/config Config system, migrations, profiles area/profiles Multi-profile isolation, HERMES_HOME scoping comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have platform/matrix Matrix adapter (E2EE) sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants