feat(gateway): expose platform_connect_timeout in config.yaml

Adds gateway.platform_connect_timeout (default 30s) to DEFAULT_CONFIG and
bridges it to the internal HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT env var
at gateway startup, following the existing gateway_timeout config->env
pattern. The env var remains the manual-override escape hatch and wins if
set explicitly; otherwise config.yaml supplies the value. This closes the
issue's documentation/config-surface request (#19776 suggestion 2) on top
of the adapter ready-wait fix, so users no longer need an undocumented env
var to raise the Discord connect timeout.

Refs #19776
This commit is contained in:
kshitijk4poor 2026-07-01 01:02:03 +05:30 committed by Teknium
parent 46ab06c238
commit 7b12753948
4 changed files with 54 additions and 2 deletions

View file

@ -1589,6 +1589,18 @@ if _config_path.exists():
_trust_recent_seconds = _gateway_cfg.get("trust_recent_files_seconds")
if _trust_recent_seconds is not None:
os.environ["HERMES_MEDIA_TRUST_RECENT_SECONDS"] = str(_trust_recent_seconds)
# Bridge gateway.platform_connect_timeout → the internal env var the
# connect path + Discord adapter ready-wait both read (#19776).
# Unlike the agent.*/display.* bridges above (config-authoritative),
# this env var is the manual-override escape hatch, so it WINS if
# already set explicitly; otherwise config.yaml supplies the value.
if (
"platform_connect_timeout" in _gateway_cfg
and not os.environ.get("HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT", "").strip()
):
os.environ["HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT"] = str(
_gateway_cfg["platform_connect_timeout"]
)
except Exception as _bridge_err:
# Previously this was silent (`except Exception: pass`), which
# hid partial bridge failures and let .env defaults shadow

View file

@ -2686,6 +2686,17 @@ DEFAULT_CONFIG = {
# Gateway settings — control how messaging platforms (Telegram, Discord,
# Slack, etc.) deliver agent-produced files as native attachments.
"gateway": {
# Seconds the gateway waits for a single messaging platform to finish
# connecting during startup (and on reconnect). Discord in particular
# can blow past the old fixed 30s when an account has many slash
# commands to sync (#19776: 90-173 skills → ~28-31s sync). Raise this
# if your gateway hits "discord connect timed out" / "Timeout waiting
# for connection to Discord" restart loops. ``0`` or negative disables
# the timeout entirely (wait indefinitely). Bridged at startup to the
# internal HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT env var, which still
# works as a manual override and wins if set explicitly.
"platform_connect_timeout": 30,
# Scale-to-zero idle detection (Phase 0). The gateway watches for idle
# and, when an instance is opted in via the NAS "Labs" toggle (carried as
# the HERMES_SCALE_TO_ZERO env stamp) AND messaging is relay-only/absent

View file

@ -46,6 +46,7 @@ def _run_gateway_import(hermes_home: Path, initial_env: dict[str, str]) -> dict[
"HERMES_AGENT_TIMEOUT_WARNING",
"HERMES_GATEWAY_BUSY_INPUT_MODE",
"HERMES_GATEWAY_BUSY_TEXT_MODE",
"HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT",
"HERMES_TIMEZONE",
):
v = os.environ.get(k)
@ -81,13 +82,15 @@ def _run_gateway_import(hermes_home: Path, initial_env: dict[str, str]) -> dict[
def _write_config(home: Path, agent_cfg: dict | None = None, display_cfg: dict | None = None,
timezone: str | None = None) -> None:
timezone: str | None = None, gateway_cfg: dict | None = None) -> None:
import yaml
cfg: dict = {}
if agent_cfg:
cfg["agent"] = agent_cfg
if display_cfg:
cfg["display"] = display_cfg
if gateway_cfg:
cfg["gateway"] = gateway_cfg
if timezone:
cfg["timezone"] = timezone
(home / "config.yaml").write_text(yaml.safe_dump(cfg))
@ -174,3 +177,29 @@ def test_env_value_survives_when_config_omits_key(hermes_home: Path) -> None:
env = _run_gateway_import(hermes_home, initial_env={})
assert env.get("HERMES_MAX_ITERATIONS") == "123"
def test_config_platform_connect_timeout_supplies_env_when_unset(hermes_home: Path) -> None:
"""config.yaml:gateway.platform_connect_timeout supplies the env var when
it isn't already set (#19776 — config surface for the Discord connect
timeout, replacing the undocumented env-var-only workaround)."""
_write_config(hermes_home, gateway_cfg={"platform_connect_timeout": 90})
env = _run_gateway_import(hermes_home, initial_env={})
assert env.get("HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT") == "90"
def test_env_platform_connect_timeout_wins_over_config(hermes_home: Path) -> None:
"""Unlike the agent.*/display.*/timezone bridges (config-authoritative),
HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT is the manual-override escape hatch:
an explicitly-set env var WINS over config.yaml. This divergence is
intentional (#19776) — the env var is the operator's emergency knob."""
_write_config(hermes_home, gateway_cfg={"platform_connect_timeout": 90})
env = _run_gateway_import(
hermes_home,
initial_env={"HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT": "120"},
)
assert env.get("HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT") == "120"

View file

@ -686,7 +686,7 @@ Advanced per-platform knobs for throttling the outbound message batcher. Most us
| `HERMES_VISION_DOWNLOAD_TIMEOUT` | Timeout in seconds for downloading an image before handing it to vision models (default: `30`). |
| `HERMES_VISION_MAX_CONCURRENCY` | Max concurrent image **encode/resize** bursts across the whole process (override for `auxiliary.vision.max_concurrency`; default: host CPU core count, no ceiling). Bounds only the CPU-bound encode step so a video-frame fan-out can't saturate every core and starve the event loop — the LLM calls stay fully concurrent. Values `< 1` are ignored. |
| `HERMES_RESTART_DRAIN_TIMEOUT` | Gateway: seconds to wait for active runs to drain on `/restart` before forcing the restart (default: `900`). |
| `HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT` | Per-platform connect timeout during gateway startup (seconds). |
| `HERMES_GATEWAY_PLATFORM_CONNECT_TIMEOUT` | Per-platform connect timeout during gateway startup and reconnect (seconds; `0`/negative waits indefinitely). Applies to the connect attempt *and* the Discord adapter's ready-wait, so accounts with many slash commands to sync don't get killed mid-startup. Bridged from `gateway.platform_connect_timeout` in `config.yaml` (default `30`); this env var is the manual override and wins if set explicitly. |
| `HERMES_GATEWAY_BUSY_INPUT_MODE` | Default gateway busy-input behavior: `queue`, `steer`, or `interrupt`. Can be overridden per chat with `/busy`. |
| `HERMES_GATEWAY_BUSY_ACK_ENABLED` | Whether the gateway sends an acknowledgment message (⚡/⏳/⏩) when a user sends input while the agent is busy (default: `true`). Set to `false` to suppress these messages entirely — the input is still queued/steered/interrupts as normal, only the chat reply is silenced. Bridged from `display.busy_ack_enabled` in `config.yaml`. |
| `HERMES_GATEWAY_NO_SUPERVISE` | Inside the s6-overlay Docker image, opt out of auto-supervision when running `hermes gateway run` and use pre-s6 foreground semantics (no auto-restart, gateway is the container's main process). Truthy values: `1`, `true`, `yes`. Equivalent to the `--no-supervise` CLI flag. No-op outside the s6 image. |