fix(platforms): per-profile adapter settings resolve env → own YAML → default under multiplex (#108440 review, salvage #109036 #109477 #110109 #110111 #110131) - #110293
Merged
Conversation
૮ >ﻌ< ა ci reviewran on 021100b — test(whatsapp): an explicit empty free_response_chats list i
|
…ed YAML default 545e74d 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 2bd5a0a)
…ofiles #69090 scoped MATRIX_RECOVERY_KEY itself (via _scoped_recovery_key()) so a secondary profile resolves its own recovery key under multiplex, but left its sibling, MATRIX_RECOVERY_KEY_OUTPUT_FILE, on a bare os.getenv(). _recovery_key_output_path() is called from inside _verify_or_bootstrap_cross_signing(), which runs fully inside _profile_runtime_scope for a secondary profile: when that profile bootstraps a new recovery key, it either doesn't get written to a file at all, or gets written to the default profile's configured path, depending on which one has the env var set. Route it through the same _get_scoped_secret() helper _scoped_recovery_key() already uses. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit fb765ee)
Every other WEIXIN_* tunable in this __init__ block (dm_policy, group_policy, rate_limit_circuit_*, send_chunk_*) already reads extra-first with a scoped-secret fallback via _extra_or_secret(). This one field was missed and still fell back to a bare os.getenv(), so a secondary profile without its own split_multiline_messages setting silently inherited the default profile's process-env value instead of the coded default. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 44e3c0d)
A2A_PORT and A2A_ADVERTISED_TOOLSETS are already captured at construction time (inside _profile_runtime_scope) via _get_scoped_secret(), but A2A_PUBLIC_URL was still read with a bare os.getenv() inside A2ARequestHandler._request_public_url() - which runs on ThreadingHTTPServer's per-connection OS thread, not the constructing thread. Raw threading.Thread never inherits contextvars, so even swapping the reader to _get_scoped_secret() at that call site would not help: the request thread has no scope, secret_scope falls back to os.environ either way. The value must be captured once at construction time (which does run in profile scope) and threaded through as instance state instead - same fix shape as A2A_PORT above. A secondary multiplex profile without its own A2A_PUBLIC_URL now falls back to the X-Forwarded-Host/Host-derived URL (or the bind host) instead of silently advertising the default profile's public URL in its Agent Card / discovery response. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> (cherry picked from commit 0c36aca)
(cherry picked from commit b3e2329)
(cherry picked from commit c98bba0)
…fault, per profile One reader (gateway.platforms._shared.extra_or_secret) now implements the precedence every per-profile setting follows for the OWNING profile: explicit scoped env/.env → that profile's config.yaml (PlatformConfig.extra) → the adapter's default. A scoped miss returns the default, never the launch process's os.environ; single-profile / default-profile installs keep the documented env-over-YAML contract. Why: 545e74d (#108705) stopped bridging a secondary's YAML into the process env and moved readers to config.extra, but the shared reader and the hand-rolled helpers in Discord/Slack/Matrix/Telegram consulted YAML FIRST and then fell back to a scoped env read. Two bug classes followed (#108440 post-merge review by andrexibiza, #109032): - an explicit env value could no longer beat YAML for the owning profile (DISCORD_ALLOW_MENTION_EVERYONE=false lost to allow_mentions.everyone: true; TELEGRAM_REACTIONS=true lost to the stock reactions: false); - a secondary that OMITTED a key inherited the launch profile's bridged env through the fallback (Matrix process_notices/session_scope, Discord auto_thread/reactions/mentions, Slack reactions/ignored_channels). Consumers migrated to the shared reader: Discord _build_allowed_mentions and _extra_or_env_flag; Slack _slack_allow_bots, _reactions_enabled (the _extra_or_env_* getters already used it); Matrix _extra_truthy, _extra_csv_set, session_scope, reactions, require_mention parsers, and — new — the allowed_users / ignore_user_patterns consumers that never read the seeded YAML lists; Telegram _extra_bool, _extra_str_set, _reactions_enabled; Feishu allow_bots; WhatsApp dm_policy/group_policy. Refs #108440, #109032
…onstruction without an env bridge 545e74d correctly stopped writing telegram.proxy_url into TELEGRAM_PROXY for a multiplexed secondary, but _build_ptb_requests still resolved the proxy only from that env var, so the secondary silently connected direct (or via the default's proxy). #100448 had deliberately left this bridge unscoped for that reason; this finishes the consumer migration instead. _apply_yaml_config seeds proxy_url into extra and resolve_proxy_url gains a `configured` rung: scoped TELEGRAM_PROXY → the profile's YAML → HTTPS_PROXY/ HTTP_PROXY/ALL_PROXY (trust_env) → macOS system proxy, with NO_PROXY semantics unchanged. Refs #108440 (finding 6)
…e's YAML policy
GatewayAuthorizationMixin._chat_scoped_grant read only the scoped
{PLATFORM}_ALLOW_BOTS env var, so a secondary whose config.yaml said
`allow_bots: all` was admitted by its own adapter and then denied centrally
(Discord, Slack Workflow posts with user=None, Feishu, Telegram). The gate now
resolves the routed adapter's effective policy with the same reader as intake:
scoped env → adapter YAML → none. Mention requirement and loop guard are
unchanged.
Refs #108440 (finding 7)
…d updates the live config For a multiplexed secondary the middleware wrote a top-level YUANBAO_HOME_CHANNEL key that load_gateway_config never reads and skipped the (correctly suppressed) process-env write, so cron and home-channel delivery had no target in-process and none after a reload either. Persist through the gateway's persist_home_channel (the profile-aware config path every /sethome uses) and set the live PlatformConfig.home_channel; the process env is still untouched under a secondary's scope. Refs #108440 (ehz0ah inline, gateway/platforms/yuanbao.py)
…ow_from, not the launch env's _bridge_env copied os.environ (the default profile's WHATSAPP_* values under multiplex) and only overlaid scoped hits, so a secondary with YAML `dm_policy: pairing` launched its Node bridge under the default profile's `allowlist` policy and the bridge rejected valid pairing DMs before Python saw them. The child env now carries the values the adapter resolved (scoped env → own YAML → default); a scoped miss removes the key rather than inheriting it. Refs #108440 (ehz0ah inline, plugins/platforms/whatsapp/adapter.py)
… its consumers Real loader + real adapter constructors under _profile_runtime_scope: a secondary reads its own YAML lists/flags and never the launch env on a miss; explicit env beats YAML for the owning profile; the central allow_bots gate agrees with the adapter; Matrix YAML lists gate intake and approval; Yuanbao home channel is live and reloadable; the WhatsApp bridge env carries the secondary's policy. All eight cases red on origin/main.
… default) Multi-profile guide gains the rule and the consumers it covers; the adapter authoring guide and the Slack allow_bots page no longer claim YAML wins.
… own YAML like every sibling Rebase reconciliation with main's JSON-allowlist decoding (#109423): the two remaining readers that consulted config.extra before the env var now follow the per-profile precedence rule (explicit scoped env → the profile's YAML → default), and ignored_threads still decodes a JSON-string list after the read. The Matrix blank-YAML test asserted YAML-over-env, the old precedence #108440's review flagged; it now pins the contract: explicit env beats YAML, a blank env value is unset (YAML applies), YAML beats the default, and an explicit empty list is a real "no rooms" value.
…d without an explicit env value Under env → YAML → default an explicit env CSV beats the YAML list; the test now blanks the env (blank env = unset) before asserting that [] is a real 'no chats' value.
teknium1
force-pushed
the
fix/mux-triage-adapter-config
branch
from
September 13, 2026 22:30
0da507d to
021100b
Compare
This was referenced Sep 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Under
gateway.multiplex_profiles, every adapter setting now resolves for the owning profile as explicit scoped env/.env → that profile'sconfig.yaml→ the adapter's default, through one shared reader — a secondary never inherits the launch process's env on a miss, and single-profile installs keep the documented env-over-YAML contract.Resolves the andrexibiza / ehz0ah post-merge review of #108440 (findings 3–7 + both inline bugs) and salvages the community PRs in the same adapter-config cluster. Terminal-scope findings 1–2 ship separately in #110207.
The rule (applied uniformly)
gateway.platforms._shared.extra_or_secret(extra, key, ENV, default)is the ONE per-profile setting reader:ENVviaget_scoped_secret.env; blank = unsetPlatformConfig.extra[key])false/0is a real valuedefaultos.environUnscoped (single-profile / default profile) the env rung reads
os.environ, soDISCORD_ALLOW_MENTION_EVERYONE=falsebeatsallow_mentions.everyone: trueandTELEGRAM_REACTIONS=truebeats the stockreactions: falseexactly as each platform page documents.Changes
extrabut kept a scoped-env fallback, so explicit env could not override YAML and a secondary that omitted a key fell through to the default profile's bridged env)._build_allowed_mentions,_extra_or_env_flag; Slack_slack_allow_bots,_reactions_enabled; Matrix_extra_truthy,_extra_csv_set,session_scope,reactions,require_mentionparsers, and theallowed_users/ignore_user_patternsconsumers that never read the seeded YAML lists (finding 5); Telegram_extra_bool,_extra_str_set,_reactions_enabled; Feishuallow_bots; WhatsAppdm_policy/group_policy.proxy_urlseeded intoextra;resolve_proxy_url(..., configured=)gains the YAML rung so a secondary's proxy reaches bothHTTPXRequests without the (correctly suppressed)TELEGRAM_PROXYbridge.allow_botsgate (finding 7):_chat_scoped_grantresolves the routed adapter's effective policy with the same reader, so Discord/Slack/Feishu/Telegram YAMLallow_bots: allon a secondary is admitted centrally too; mention requirement and loop guard unchanged.platforms.yuanbao.home_channelviapersist_home_channel(whatload_gateway_configreads back) and updates the livePlatformConfig; process env still untouched under a secondary._bridge_envhands bridge.js the adapter's resolveddm_policy/allow_fromand drops inheritedWHATSAPP_*keys on a scoped miss.telegram.reactions: false(regressed in 545e74d0ea) #109032), fix(matrix): scope MATRIX_RECOVERY_KEY_OUTPUT_FILE under multiplex profiles #110111 / fix(weixin): scope split_multiline_messages under multiplex profiles #110109 / fix(a2a): scope A2A_PUBLIC_URL per multiplex profile #110131 @EloquentBrush0x (Matrix recovery-key path, Weixinsplit_multiline_messages, A2A public URL — rawos.getenvleaks), fix(discord): preserve transport owner for thread renames #109477 @benjamin-rousseau-shift (run_topics.pytransport-ref hunk only; the_transport_adapter_profilecommit overlaps 77180ac and was not taken). Salvaged test additions trimmed to two invariants each.allow_botspage no longer claim YAML wins.Validation
Review probes (
contract_probes.pyfrom the #108440 review, fresh process each, temp HOME, realload_gateway_config+ real adapter constructors under_profile_runtime_scope):discord_precedenceenv=false vs YAML trueparse: ["everyone","users"]parse: ["users"]ambient_readerssecondary omits keysmatrix_readersseeded listsallowed_user_ids: [], ignored sender dispatched, owner cannot approve["@owner"], 0 dispatched, owner approvestelegram_proxysecondary YAML proxy[null, null][proxy, proxy]bots_authYAMLallow_bots: allall, gatewayfalsetrueNonedm:tenant-b2, env untoucheddm_policy: pairingallowlist+ default's allowlistpairing, no allowlistTests:
tests/gateway/test_adapter_settings_scoped_precedence.py(6 invariants) + 2 intest_shared_platform_boilerplate.py— all 8 red on origin/main via source swap, green here.scripts/run_tests.sh tests/gateway tests/plugins tests/tools tests/hermes_cli tests/agent tests/cron: 41,989 passed; the 8 failures (modal/parallel-weblazy-install, live-guard, update-gate) fail identically on origin/main. ruff / windows-footguns / compat-pointers /git diff --checkclean.Refs #108440, #109032, #109475, #110111, #110109, #110131, #107442. Related: #110207 (terminal scope).
Infographic