Skip to content

fix(gateway): honor platforms.signal.enabled=false over SIGNAL_* env vars - #11103

Closed
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/gateway-signal-enabled-yaml-precedence
Closed

fix(gateway): honor platforms.signal.enabled=false over SIGNAL_* env vars#11103
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/gateway-signal-enabled-yaml-precedence

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes the issue #11096 Bug 3 precedence bug where setting platforms.signal.enabled: false in config.yaml was silently overwritten by SIGNAL_HTTP_URL + SIGNAL_ACCOUNT env vars. The operator who filed the bug spent ~30 min diagnosing — the YAML flag was never honored, and the only workaround was to comment out the env vars.

Root cause. _apply_env_overrides in gateway/config.py unconditionally assigned config.platforms[Platform.SIGNAL].enabled = True whenever both env vars were set, regardless of what config.yaml had already loaded into config.platforms[Platform.SIGNAL].enabled via GatewayConfig.from_dict().

Smallest safe fix. enabled=True is now only applied when Signal is not already declared in config.platforms (fresh PlatformConfig). When platforms.signal is in config.yaml — including with enabled: false — the YAML-declared flag is preserved. Env vars continue to populate extra.http_url, extra.account, and extra.ignore_stories, so a later YAML flip back to enabled: true picks up current env-supplied connection details without another export cycle.

Related Issue

Fixes part of #11096 (Bug 3 — config.yaml signal.enabled: false is silently ignored when SIGNAL_* env vars are set).

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • gateway/config.py — Signal block in _apply_env_overrides: only force enabled=True on fresh PlatformConfig creation; preserve YAML-declared .enabled when the platform already exists. Still updates extra so env-supplied connection details are retained.
  • tests/gateway/test_config.py — three regression tests covering YAML-disabled + env vars (the bug), YAML-enabled + env vars (mixed), and env vars alone (preserve documented behavior for purely env-driven Signal configuration).

Backward compatibility / precedence

  • YAML absent: SIGNAL_HTTP_URL + SIGNAL_ACCOUNT still enable Signal end-to-end. Documented .env-only configuration path is unchanged.
  • YAML enabled: true + env vars: Signal stays enabled; env vars continue to fill in extra.http_url / extra.account. Unchanged.
  • YAML enabled: false + env vars (the bug): Signal now stays disabled as the operator intended. extra is still populated so a later YAML flip doesn't require re-exporting env vars.
  • YAML platforms.signal: {} (empty block) + env vars: Signal remains at its YAML-derived default (enabled=False). Users who declare a platform block in YAML opt into YAML-authoritative behavior; set enabled: true explicitly to combine with env-supplied details. This is the same precedence principle Three bugs in v0.9.0: context_length override, thinking-block sessions, config.yaml vs env precedence #11096 recommends.

Narrow scope — why only Signal

The same enabled = True pattern exists across other blocks in _apply_env_overrides (Discord, Mattermost, Matrix, etc. — 19 total). I deliberately kept this PR narrow to the platform the issue flagged so the fix is small, easy to review, and easy to revert if the precedence semantics need tuning. Once the precedence model is agreed, the same treatment can extend to the rest in follow-up PRs.

How to Test

Repro on main (fails before this PR):

# ~/.hermes/config.yaml
platforms:
  signal:
    enabled: false
export SIGNAL_HTTP_URL=http://localhost:9090
export SIGNAL_ACCOUNT=+15551234567
python -c 'from gateway.config import load_gateway_config, Platform; c=load_gateway_config(); print(c.platforms[Platform.SIGNAL].enabled)'
# main: True  (bug — YAML opt-out ignored)
# this PR: False

Regression suite:

source venv/bin/activate
python -m pytest tests/gateway/test_config.py -q -k signal
# 3 passed

Validation

All commands run with source venv/bin/activate:

  • python -m pytest tests/gateway/test_config.py -q -k signal — 3 passed (new regression tests)
  • python -m pytest tests/gateway/test_config.py tests/gateway/test_signal.py -q — 87 passed
  • python -m pytest tests/gateway/ -q --tb=short — 3016 passed, 1 skipped. The 2 residual failures (test_approve_deny_commands.py::TestBlockingApprovalE2E::test_blocking_approval_approve_once and ::test_blocking_approval_deny) also fail on clean origin/main — they are pre-existing and unrelated. A third flaky failure (test_non_internal_event_without_user_triggers_pairing) passes in isolation on this branch; it is a test-ordering issue unrelated to this change.
  • git diff --check — clean
  • python -m py_compile gateway/config.py tests/gateway/test_config.py — OK

Tested on macOS (Darwin 25.5.0), Python 3.11.15.

No standalone lint/typecheck command is defined in the contributor docs; the Tests workflow (.github/workflows/tests.yml) runs python -m pytest tests/ -q --ignore=tests/integration --ignore=tests/e2e --tb=short -n auto, matching what CI will execute.

Checklist

Copilot AI review requested due to automatic review settings April 16, 2026 15:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a config precedence bug in the messaging gateway where platforms.signal.enabled: false in config.yaml was being overridden by SIGNAL_HTTP_URL + SIGNAL_ACCOUNT environment variables.

Changes:

  • Update gateway/config.py so Signal is only force-enabled from env vars when Signal is not already present in config.platforms.
  • Continue populating Signal extra fields from env vars even when YAML disables Signal, so re-enabling later in YAML uses current env connection details.
  • Add regression tests covering YAML-disabled + env, YAML-enabled + env, and env-only configuration.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
gateway/config.py Adjust Signal env override logic to preserve YAML-declared enabled while still filling extra connection details.
tests/gateway/test_config.py Add regression tests validating the updated Signal precedence behavior across common config/env combinations.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread gateway/config.py Outdated
Comment on lines +853 to +860
# Not declared in config.yaml — env vars alone enable Signal.
config.platforms[Platform.SIGNAL] = PlatformConfig(enabled=True)
# When platforms.signal is already declared in config.yaml, preserve
# its ``enabled`` value (including an explicit ``enabled: false``) so
# env vars only supply connection defaults, not override the YAML
# opt-out. Env vars still update ``extra`` so a re-enable in YAML
# picks up the current env-supplied url/account without another
# restart cycle. See issue #11096 (Bug 3).

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says this preserves enabled only when Signal is declared in config.yaml, but the condition is Platform.SIGNAL not in config.platforms, which also treats legacy gateway.json entries as “declared” and will therefore prevent SIGNAL_HTTP_URL/SIGNAL_ACCOUNT from auto-enabling Signal in that case. If that’s intended, please update the comment (and ideally add a regression test for the gateway.json path); if it’s not intended, consider distinguishing YAML-declared platforms from legacy defaults before deciding whether to force enabled=True.

Suggested change
# Not declared in config.yaml — env vars alone enable Signal.
config.platforms[Platform.SIGNAL] = PlatformConfig(enabled=True)
# When platforms.signal is already declared in config.yaml, preserve
# its ``enabled`` value (including an explicit ``enabled: false``) so
# env vars only supply connection defaults, not override the YAML
# opt-out. Env vars still update ``extra`` so a re-enable in YAML
# picks up the current env-supplied url/account without another
# restart cycle. See issue #11096 (Bug 3).
# No existing Signal platform entry was loaded, so env vars alone
# enable Signal.
config.platforms[Platform.SIGNAL] = PlatformConfig(enabled=True)
# When a Signal platform entry already exists in ``config.platforms``
# (for example from config.yaml or legacy gateway.json loading),
# preserve its ``enabled`` value, including an explicit
# ``enabled: false``. In that case env vars only supply connection
# defaults and do not override the existing opt-out. Env vars still
# update ``extra`` so a later re-enable picks up the current
# env-supplied url/account without another restart cycle. See issue
# #11096 (Bug 3).

Copilot uses AI. Check for mistakes.
@briandevans

Copy link
Copy Markdown
Contributor Author

Addressed the Copilot review in 909f8d2a:

  • Updated the inline comment to honestly name both sources (config.yaml and legacy gateway.json). The condition was already correct — any entry already in config.platforms (YAML or legacy JSON) gets its enabled preserved.
  • Added test_signal_gateway_json_disabled_beats_env_vars so the gateway.json-only opt-out path is covered. A user who set Signal enabled: false in legacy gateway.json and never migrated to config.yaml now keeps the opt-out across restarts with SIGNAL_HTTP_URL/SIGNAL_ACCOUNT in their .env.

Precedence truth table is unchanged:

  • YAML absent + env-only → Signal enabled by env (unchanged).
  • YAML (or legacy JSON) enabled: false → stays off, env only fills extra (the bug fix).
  • YAML (or legacy JSON) enabled: true → stays on, env fills extra (unchanged).

Validation: python -m pytest tests/gateway/test_config.py -q -k signal -> 4 passed.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/signal Signal CLI adapter area/config Config system, migrations, profiles labels Apr 25, 2026
briandevans and others added 2 commits May 4, 2026 17:12
SIGNAL_HTTP_URL + SIGNAL_ACCOUNT env vars unconditionally overwrote
config.platforms[Platform.SIGNAL].enabled to True inside
_apply_env_overrides, clobbering an explicit platforms.signal.enabled=false
set in config.yaml. Operators wiring Signal deliberately to a separate
service saw it silently re-enabled; diagnosing required removing the env
vars because logs surfaced no hint the YAML flag had been dropped.

The env-var block now only forces enabled=True when platforms.signal is
absent from config.yaml (fresh PlatformConfig). When the YAML already
declared the platform — including with enabled=false — the declared flag
is preserved. Env vars still populate extra.http_url / extra.account /
extra.ignore_stories, so a later enabled=true flip reuses the current
env-supplied connection details without needing to re-export.

Narrow scope: Signal only. The same pattern exists across several other
platform blocks in _apply_env_overrides and is intentionally left for a
follow-up so this change is small and easy to review.

Regression tests added to tests/gateway/test_config.py:
- test_signal_yaml_disabled_beats_env_vars
- test_signal_yaml_enabled_preserved_with_env_vars
- test_signal_env_vars_alone_enable_platform

Fixes part of NousResearch#11096 (Bug 3).
Addresses the Copilot review note on PR NousResearch#11103: the previous comment
claimed the preservation applied only when Signal was declared in
``config.yaml``, but the actual condition (``Platform.SIGNAL not in
config.platforms``) covers any existing persisted source — including
legacy ``gateway.json`` — because JSON is merged into ``config.platforms``
before ``_apply_env_overrides`` runs.

- Update the inline comment to reflect the real semantics.
- Add ``test_signal_gateway_json_disabled_beats_env_vars`` regression
  test so the JSON-only opt-out path is covered.

Validation: ``python -m pytest tests/gateway/test_config.py -q -k signal``
-> 4 passed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@briandevans
briandevans force-pushed the fix/gateway-signal-enabled-yaml-precedence branch from 909f8d2 to 9c9d0e3 Compare May 5, 2026 00:12
@briandevans

Copy link
Copy Markdown
Contributor Author

Rebased onto current origin/main (b816fd4e2) — branch was 2.8k commits stale. Clean rebase, no conflicts; touched files (gateway/config.py, tests/gateway/test_config.py) are unaffected by the recent gateway feature work (Slack format_message, IRC plugin registry, Telegram require_mention bridging, etc).

Focused tests still pass on the rebased base:

uv run --with pytest --with pytest-xdist --with pytest-asyncio python3 -m pytest tests/gateway/test_config.py -v
# 44 passed, 44 warnings in 1.47s

The four signal-precedence tests added by this PR all pass:

  • test_signal_yaml_enabled_preserved_with_env_vars
  • test_signal_yaml_disabled_beats_env_vars
  • test_signal_gateway_json_disabled_beats_env_vars
  • test_signal_env_vars_alone_enable_platform

The bug is still present on origin/maingateway/config.py:1170 (current main) still does config.platforms[Platform.SIGNAL].enabled = True unconditionally when both SIGNAL_HTTP_URL and SIGNAL_ACCOUNT are set, overriding any platforms.signal.enabled: false opt-out from config.yaml or legacy gateway.json.

@briandevans

Copy link
Copy Markdown
Contributor Author

Closing to keep the queue clean — happy to reopen if this is still useful.

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 comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/signal Signal CLI adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants