From d92490a4e4e45690ae9259a4625f0dee318811a5 Mon Sep 17 00:00:00 2001 From: liuhao1024 Date: Sat, 12 Sep 2026 19:39:32 +0800 Subject: [PATCH] fix(telegram): let an explicit TELEGRAM_REACTIONS beat the materialized YAML default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 545e74d0ea made _reactions_enabled consult extra.reactions before the env var, and _apply_yaml_config seeds extra["reactions"] whenever the YAML key is present — including the stock reactions: false every install materializes. The documented TELEGRAM_REACTIONS=true switch therefore became a silent no-op after the 0.21.2 update (#109032), contradicting yaml_env_setter's "explicit env wins over YAML" contract. Read the scoped env first and fall back to the profile's own YAML: under multiplex a scoped miss returns the default instead of another profile's process-env value (#72348), so only a scoped/env hit counts as explicit and per-profile isolation is unchanged. Fixes #109032 (cherry picked from commit 2bd5a0a5c0a5f9630fd82f133def65f225f653a3) --- plugins/platforms/telegram/adapter.py | 15 ++++-- tests/gateway/test_telegram_reactions.py | 69 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/plugins/platforms/telegram/adapter.py b/plugins/platforms/telegram/adapter.py index 1258c83fd8..f8f1f3774b 100644 --- a/plugins/platforms/telegram/adapter.py +++ b/plugins/platforms/telegram/adapter.py @@ -6392,10 +6392,19 @@ class TelegramAdapter(BasePlatformAdapter): # -- Message reactions (processing lifecycle) -- def _reactions_enabled(self) -> bool: - """Reactions enabled via ``extra.reactions`` (YAML, per profile) or TELEGRAM_REACTIONS.""" - configured = self.config.extra.get("reactions") + """Reactions enabled via TELEGRAM_REACTIONS or ``extra.reactions`` (YAML, per profile). + + An explicitly set env var wins over YAML — the same rule ``yaml_env_setter`` documents for + the YAML→env bridge — so the stock ``reactions: false`` every install materializes cannot + silently kill a documented ``TELEGRAM_REACTIONS=true`` (#109032). Under multiplex a scoped + miss returns the default instead of another profile's process-env value (#72348), so only + a scoped/env hit counts as explicit; otherwise the profile's own YAML decides. + """ + configured = _scoped_gate_env("TELEGRAM_REACTIONS", "") + if not configured: + configured = self.config.extra.get("reactions") if configured is None: - configured = _scoped_gate_env("TELEGRAM_REACTIONS", "false") + return False return str(configured).lower() not in {"false", "0", "no"} async def _set_reaction(self, chat_id: str, message_id: str, emoji: Optional[str]) -> bool: diff --git a/tests/gateway/test_telegram_reactions.py b/tests/gateway/test_telegram_reactions.py index 4c2377db37..38d8512293 100644 --- a/tests/gateway/test_telegram_reactions.py +++ b/tests/gateway/test_telegram_reactions.py @@ -53,6 +53,75 @@ def test_reactions_enabled_when_set_true(monkeypatch): assert adapter._reactions_enabled() is True +def test_explicit_env_wins_over_materialized_yaml_default(monkeypatch): + """TELEGRAM_REACTIONS=true must beat the stock ``reactions: false`` in config.yaml (#109032). + + Fresh installs materialize the whole default config tree, so ``_apply_yaml_config`` seeds + ``extra["reactions"] = False`` even when the user never chose a value; the reader must still + honour the explicitly set env var, like ``yaml_env_setter`` documents for the bridge. + """ + monkeypatch.setenv("TELEGRAM_REACTIONS", "true") + adapter = _make_adapter() + adapter.config.extra["reactions"] = False + assert adapter._reactions_enabled() is True + + +def test_bridged_yaml_false_without_explicit_env_still_disables(monkeypatch): + """With no explicit env the YAML→env bridge writes 'false'; reactions stay off.""" + monkeypatch.setenv("TELEGRAM_REACTIONS", "false") + adapter = _make_adapter() + adapter.config.extra["reactions"] = False + assert adapter._reactions_enabled() is False + + +def test_yaml_true_enables_when_env_unset(monkeypatch): + """An explicit ``reactions: true`` in config.yaml enables reactions without any env var.""" + monkeypatch.delenv("TELEGRAM_REACTIONS", raising=False) + adapter = _make_adapter() + adapter.config.extra["reactions"] = True + assert adapter._reactions_enabled() is True + + +def test_explicit_env_false_wins_over_yaml_true(monkeypatch): + """An explicit TELEGRAM_REACTIONS=false also wins over a YAML ``reactions: true``.""" + monkeypatch.setenv("TELEGRAM_REACTIONS", "false") + adapter = _make_adapter() + adapter.config.extra["reactions"] = True + assert adapter._reactions_enabled() is False + + +def test_scoped_miss_does_not_leak_default_profile_env(monkeypatch): + """Under multiplex a scoped miss must not read another profile's process-env value (#72348).""" + from agent.secret_scope import reset_secret_scope, set_multiplex_active, set_secret_scope + + monkeypatch.setenv("TELEGRAM_REACTIONS", "true") # default profile's bridged value + adapter = _make_adapter() + adapter.config.extra["reactions"] = False # this profile's own YAML + set_multiplex_active(True) + token = set_secret_scope({"TELEGRAM_BOT_TOKEN": "222:b2"}) + try: + assert adapter._reactions_enabled() is False + finally: + reset_secret_scope(token) + set_multiplex_active(False) + + +def test_scoped_env_hit_wins_over_own_yaml(monkeypatch): + """A secondary profile's own scoped TELEGRAM_REACTIONS=true beats its YAML ``reactions: false``.""" + from agent.secret_scope import reset_secret_scope, set_multiplex_active, set_secret_scope + + monkeypatch.setenv("TELEGRAM_REACTIONS", "false") # default profile's value + adapter = _make_adapter() + adapter.config.extra["reactions"] = False + set_multiplex_active(True) + token = set_secret_scope({"TELEGRAM_BOT_TOKEN": "222:b2", "TELEGRAM_REACTIONS": "true"}) + try: + assert adapter._reactions_enabled() is True + finally: + reset_secret_scope(token) + set_multiplex_active(False) + + # ── _set_reaction ────────────────────────────────────────────────────