fix(matrix): blank YAML values fall through to env at every extra-first reader

545e74d0 (post-0.21.2) made the Matrix YAML bridge seed its values into
PlatformConfig.extra so secondary multiplex profiles read their own config. The
"csv" bridge kind seeds any non-None value, so `free_response_rooms: ''` now
reaches extra as '' — and the readers' `if raw is None` fallback no longer fires,
so MATRIX_FREE_RESPONSE_ROOMS is ignored and require_mention drops every
un-mentioned message. Before that commit the bridge only wrote env and the key
was absent from extra, so the env value applied.

Route the three identity-check readers (_extra_csv_set, _extra_truthy,
_resolve_max_message_length — the last a three-tier chain where '' also
short-circuited the plugin-registry default) through the shared
gateway.platforms._shared.extra_or_secret, whose default already treats a blank
string as unset (the idiom mattermost/dingtalk/slack readers use). Explicit
scalars, bools and lists (including []) stay authoritative.

Two invariant tests replace the salvaged suite (moved to
tests/plugins/platforms/matrix/ to mirror the source path): blank falls through
for all three readers; explicit values still beat env.

Fixes #109358
Co-authored-by: KoNit-K <124019182+KoNit-K@users.noreply.github.com>
This commit is contained in:
teknium1
2026-09-13 12:48:07 -07:00
committed by Teknium
parent 885ce3f493
commit 3ad65cc892
3 changed files with 49 additions and 82 deletions
+9 -15
View File
@@ -39,7 +39,8 @@ from typing import Any, Dict, Optional, Set
from agent.secret_scope import get_secret
from gateway.platforms._shared import (
apply_yaml_bridge as _apply_yaml_bridge, get_scoped_secret as _get_scoped_secret, send_error
apply_yaml_bridge as _apply_yaml_bridge, extra_or_secret as _extra_or_secret,
get_scoped_secret as _get_scoped_secret, send_error
)
try:
@@ -319,10 +320,8 @@ MATRIX_MAX_MESSAGE_LENGTH_CEILING = 65535
def _resolve_max_message_length(config) -> int:
"""Resolve outbound chunk size from config, env, or plugin registry."""
raw = (getattr(config, "extra", {}) or {}).get("max_message_length")
if raw is None:
raw = _get_scoped_secret("MATRIX_MAX_MESSAGE_LENGTH")
if raw is None:
raw = _extra_or_secret(getattr(config, "extra", None), "max_message_length", "MATRIX_MAX_MESSAGE_LENGTH", None)
if raw is None or not str(raw).strip():
with suppress(Exception):
from gateway.platform_registry import platform_registry
entry = platform_registry.get("matrix")
@@ -477,12 +476,9 @@ def _csv_set(raw: Any) -> Set[str]:
def _extra_csv_set(config, key: str, env_name: str) -> Set[str]:
"""Resolve a room/user list from config.extra[key], else the env var."""
raw = config.extra.get(key)
if raw is None or (not isinstance(raw, list) and not str(raw).strip()):
# Scoped read: under multiplex os.environ is the DEFAULT profile's room/user list.
raw = _get_scoped_secret(env_name, "").strip()
return _csv_set(raw)
"""Resolve a room/user list from config.extra[key] (blank = unset), else the scoped env var —
under multiplex os.environ is the DEFAULT profile's room/user list."""
return _csv_set(_extra_or_secret(config.extra, key, env_name))
def _recovery_key_output_path() -> Optional[Path]:
@@ -871,10 +867,8 @@ class MatrixAdapter(BasePlatformAdapter):
@staticmethod
def _extra_truthy(config, key: str, env_name: str, default: str) -> bool:
"""``config.extra[key]`` (YAML-bridged, per profile) else the env var, true/1/yes semantics."""
configured = config.extra.get(key)
if configured is None:
return _env_truthy(env_name, default)
"""``config.extra[key]`` (YAML-bridged, per profile; blank = unset) else the env var, true/1/yes."""
configured = _extra_or_secret(config.extra, key, env_name, default)
return configured if isinstance(configured, bool) else str(configured).lower() in ("true", "1", "yes")
@staticmethod
-67
View File
@@ -78,72 +78,6 @@ def _make_event(
)
@pytest.fixture
def matrix_free_rooms_scope(monkeypatch):
from agent import secret_scope
was_multiplexed = secret_scope.is_multiplex_active()
monkeypatch.setenv("MATRIX_FREE_RESPONSE_ROOMS", "!process:example.org")
secret_scope.set_multiplex_active(True)
token = secret_scope.set_secret_scope(
{"MATRIX_FREE_RESPONSE_ROOMS": "!scoped-a:example.org, !scoped-b:example.org"}
)
try:
yield
finally:
secret_scope.reset_secret_scope(token)
secret_scope.set_multiplex_active(was_multiplexed)
@pytest.mark.parametrize("configured_value", ["", " \t "])
def test_matrix_free_response_rooms_blank_scalar_falls_back_to_scoped_value(
configured_value,
matrix_free_rooms_scope,
):
from plugins.platforms.matrix.adapter import _extra_csv_set
config = PlatformConfig(
enabled=True,
extra={"free_response_rooms": configured_value},
)
assert _extra_csv_set(
config,
"free_response_rooms",
"MATRIX_FREE_RESPONSE_ROOMS",
) == {"!scoped-a:example.org", "!scoped-b:example.org"}
@pytest.mark.parametrize(
("configured_value", "expected"),
[
("!configured:example.org", {"!configured:example.org"}),
(
["!configured-a:example.org", " !configured-b:example.org "],
{"!configured-a:example.org", "!configured-b:example.org"},
),
],
ids=("scalar", "list"),
)
def test_matrix_free_response_rooms_explicit_values_override_scoped_fallback(
configured_value,
expected,
matrix_free_rooms_scope,
):
from plugins.platforms.matrix.adapter import _extra_csv_set
config = PlatformConfig(
enabled=True,
extra={"free_response_rooms": configured_value},
)
assert _extra_csv_set(
config,
"free_response_rooms",
"MATRIX_FREE_RESPONSE_ROOMS",
) == expected
# ---------------------------------------------------------------------------
# Mention detection helpers
# ---------------------------------------------------------------------------
@@ -448,4 +382,3 @@ class TestMatrixConfigBridge:
== "!room1:example.org,!room2:example.org"
)
assert os.getenv("MATRIX_AUTO_THREAD") == "false"
@@ -0,0 +1,40 @@
"""A present-but-blank ``matrix:`` key in config.yaml means "unset": the env-var fallback must fire
exactly as it does when the key is absent (0.21.2 started seeding blank YAML values into
``config.extra``, which flipped the precedence and silently disabled free-response rooms)."""
import pytest
from gateway.config import PlatformConfig
@pytest.mark.parametrize("blank", ["", " \t "])
def test_blank_yaml_values_fall_through_to_env(monkeypatch, blank):
from plugins.platforms.matrix.adapter import MatrixAdapter, _extra_csv_set, _resolve_max_message_length
monkeypatch.setenv("MATRIX_FREE_RESPONSE_ROOMS", "!home:example.org")
monkeypatch.setenv("MATRIX_MAX_MESSAGE_LENGTH", "9000")
monkeypatch.setenv("MATRIX_AUTO_THREAD", "false")
config = PlatformConfig(enabled=True, extra={
"free_response_rooms": blank, "max_message_length": blank, "auto_thread": blank})
assert _extra_csv_set(config, "free_response_rooms", "MATRIX_FREE_RESPONSE_ROOMS") == {"!home:example.org"}
assert _resolve_max_message_length(config) == 9000
assert MatrixAdapter._extra_truthy(config, "auto_thread", "MATRIX_AUTO_THREAD", "true") is False
def test_explicit_yaml_values_still_beat_env(monkeypatch):
from plugins.platforms.matrix.adapter import MatrixAdapter, _extra_csv_set, _resolve_max_message_length
monkeypatch.setenv("MATRIX_FREE_RESPONSE_ROOMS", "!env:example.org")
monkeypatch.setenv("MATRIX_MAX_MESSAGE_LENGTH", "9000")
monkeypatch.setenv("MATRIX_AUTO_THREAD", "true")
config = PlatformConfig(enabled=True, extra={
"free_response_rooms": ["!a:example.org", " !b:example.org "], "max_message_length": 4000,
"auto_thread": False})
assert _extra_csv_set(config, "free_response_rooms", "MATRIX_FREE_RESPONSE_ROOMS") == {"!a:example.org", "!b:example.org"}
assert _resolve_max_message_length(config) == 4000
assert MatrixAdapter._extra_truthy(config, "auto_thread", "MATRIX_AUTO_THREAD", "true") is False
# An explicit empty list is a real "no rooms" value, not "unset".
assert _extra_csv_set(PlatformConfig(enabled=True, extra={"free_response_rooms": []}),
"free_response_rooms", "MATRIX_FREE_RESPONSE_ROOMS") == set()