Skip to content

fix(gateway): wait for Telegram polling readiness before sending rest… - #65709

Closed
oppih wants to merge 3 commits into
NousResearch:mainfrom
oppih:fix/restart-notification-send-path-degraded
Closed

fix(gateway): wait for Telegram polling readiness before sending rest…#65709
oppih wants to merge 3 commits into
NousResearch:mainfrom
oppih:fix/restart-notification-send-path-degraded

Conversation

@oppih

@oppih oppih commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

…art notification

What does this PR do?

Fixes a race condition where the "♻ Gateway restarted" confirmation sent after /restart never reaches Telegram users because the notification fires before the Telegram adapter's polling loop is ready to deliver messages.

Root cause chain:

  1. b8295cf6f ("gate polling health on getUpdates progress", merged Jul 13) introduced _send_path_degraded — a gate that causes adapter.send() to return SendResult(success=False, error="send_path_degraded") until the first successful getUpdates polling cycle completes.
  2. _send_restart_notification() calls adapter.send() during gateway startup to notify the chat that issued /restart. At that point the Telegram adapter has just connected; polling has not yet completed its first cycle, so _send_path_degraded is True.
  3. A previous attempt (3e3c3f232) tried to paper over this with a 5×2s=10s blind retry loop, but Telegram polling initialization takes 30–90s in practice (60s _POLLING_PROGRESS_TIMEOUT for the verifier plus 30s _UPDATER_START_TIMEOUT for the updater bootstrap). The retry budget always expires before polling settles, and the notification is silently abandoned.

Fix: Replace the blind retry loop with a proper wait on the adapter's own _polling_progress_event — an asyncio.Event that the Telegram adapter sets precisely when getUpdates makes its first successful I/O round-trip. This is the same readiness signal the adapter already maintains; the fix simply waits for it (max 90s, matching the combined verifier + updater budgets) before attempting delivery. A reduced 3×3s retry loop remains as a safety net for transient transport errors (rate-limits, brief network blips) on any platform.

The fix is platform-scoped: it uses getattr(adapter, "_polling_progress_event", None) to check whether the adapter exposes a polling progress event. Non-Telegram adapters (Discord, Slack, WeChat, etc.) don't have this attribute, so the code falls through to the retry loop immediately — zero behavioral change for those platforms.

Related Issue

Fixes #65057/restart sends no user-facing confirmation on Telegram because the restart notification is dropped by the send_path_degraded gate introduced in b8295cf6f.

See also open PRs #64613, #65358, and #36148 that approach the same symptom but don't address the root cause (polling readiness timing).

Type of Change

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

Changes Made

  • gateway/run.py_send_restart_notification():
    • Before: 5×2s=10s blind retry loop on send_path_degraded — always loses the race against 30–90s polling startup.
    • After: await asyncio.wait_for(adapter._polling_progress_event.wait(), timeout=90.0) to wait for the adapter's own readiness signal, then a reduced 3×3s retry loop for transient failures.
    • Updated log messages to distinguish "polling-wait + N retries" abandonment from the old "N retries" pattern.
    • Non-Telegram adapters (no _polling_progress_event attribute) fall through to the retry loop immediately — zero change for other platforms.

How to Test

  1. Start the gateway connected to a Telegram bot.
  2. Send /restart from a Telegram DM.
  3. Expected: "♻ Gateway restarted successfully. Your session continues." appears in the chat within ~30–60s (once polling settles).
  4. Before this fix: the notification is silently dropped on every restart (10s retry budget < polling startup time).

Validation performed on Ubuntu 24.04 / Python 3.11:

  • Log-confirmed that the notification reached Telegram after the 90s polling-wait (previously abandoned at 5×2s=10s).
  • Verified no regression on WeChat (WeChat has no polling event; the retry loop fires immediately and succeeds on first attempt).
  • Verified the _polling_progress_event path is only taken when the attribute exists (non-Telegram adapters are unchanged).

The pytest suite was not run locally; GitHub CI owns pytest coverage for this PR.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Ubuntu 24.04, Python 3.11.15

Test results on main (no regressions):

Test file Result
tests/gateway/test_telegram_polling_progress.py 22/22 passed

Note: test_telegram_polling_progress_ptb.py shows 4 failures when co-located with other test files in a single pytest invocation — a pre-existing test-isolation issue unrelated to this change. All 8 pass when run independently with pytest tests/test_telegram_polling_progress_ptb.py.

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — N/A (Telegram adapter internals are platform-agnostic; getattr guard ensures non-Telegram platforms are unaffected)
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Screenshots / Logs

Before (retry loop, silently abandoned):

Restart notification to telegram:70922452 deferred — send path degraded (attempt 1/5), retrying in 2s
Restart notification to telegram:70922452 deferred — send path degraded (attempt 2/5), retrying in 2s
Restart notification to telegram:70922452 deferred — send path degraded (attempt 3/5), retrying in 2s
Restart notification to telegram:70922452 deferred — send path degraded (attempt 4/5), retrying in 2s
WARNING  Restart notification to telegram:70922452 abandoned after 5 retries — Telegram send path stayed degraded beyond startup timeout

After (polling-wait, confirmed delivery):

[Telegram] Connected to Telegram (polling mode)
INFO  Sent restart notification to weixin:o9cq801U...
INFO  Sent restart notification to telegram:70922452

Telegram user sees: "♻ Gateway restarted successfully. Your session continues."

@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/telegram Telegram bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 16, 2026
@alt-glitch

alt-glitch commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #64613, #65057, and #66598 for restart-notification delivery. Live scope is now focused on gateway/run.py polling-readiness retry behavior; the earlier credential-pool/model-picker bundle is no longer present. These patches target distinct lifecycle stages, so maintainers should choose the delivery strategy.

@obssian

obssian commented Jul 16, 2026

Copy link
Copy Markdown

I independently reproduced this race on Hermes Agent v0.18.2 (2026.7.7.2), Ubuntu 24.04 / Python 3.11, with a Telegram DM.

Observed before the fix on every /restart:

Restart notification to telegram:<chat> was not delivered: send_path_degraded

The gateway was already running and Telegram polling recovered afterward, but .restart_notify.json had been consumed, so the user never received the comeback notification.

I validated the same readiness-event approach locally in a real restart. Once _polling_progress_event was set, retrying the notification delivered:

♻ Gateway restarted successfully. Your session continues.

I also added a focused regression test locally in tests/gateway/test_restart_notification.py; the test failed on current main and passed after the readiness wait + one retry. The full file result was:

28 passed, 0 failed

One review note: the current PR diff doesn't appear to include the regression test mentioned in the checklist. A useful test should model polling readiness independently from adapter.send():

  1. first send returns SendResult(success=False, error="send_path_degraded") (or schedule readiness before the first send, matching this PR's order);
  2. an asyncio.Event representing _polling_progress_event is set by a separate task;
  3. the notification is delivered after readiness;
  4. .restart_notify.json is removed;
  5. non-degraded permanent failures are not retried.

It may also be worth considering the order used by the locally verified variant: attempt the send first, and wait on _polling_progress_event only when the concrete error is send_path_degraded. That avoids coupling every adapter exposing a similarly named event to a pre-send 90-second wait and keeps the readiness delay scoped to the failure that motivated this fix.

This PR addresses the correct root cause; the report above is field confirmation plus a request for explicit regression coverage.

@obssian

obssian commented Jul 16, 2026

Copy link
Copy Markdown

Follow-up after an independent review of the local regression test:

I tightened the test so it no longer makes readiness available from inside the first mocked send(). The production timing is now represented by a separate task:

  • first send returns send_path_degraded and signals that the attempt completed;
  • a separate coroutine waits for that signal, delays briefly, then sets _polling_progress_event;
  • any second send before readiness records a premature retry and fails;
  • assertions require exactly two sends, no premature retry, successful delivery, and marker cleanup.

The strengthened test still passes with the readiness-wait implementation: 28/28 tests passed in tests/gateway/test_restart_notification.py.

The independent review also raised a useful longer-term design point: gateway orchestration currently needs a private Telegram attribute plus the literal send_path_degraded error. A public adapter capability such as await adapter.wait_until_send_ready(timeout=...) (defaulting to immediate readiness in the base adapter) would keep polling generation/event replacement semantics encapsulated in the Telegram adapter. This is non-blocking for the current bug fix, but would make the contract safer to maintain.

oppih added a commit to oppih/hermes-agent that referenced this pull request Jul 17, 2026
…gression tests

Incorporate review feedback from @obssian on NousResearch#65709:

1. Code ordering: try adapter.send() first; only wait on
   _polling_progress_event when the concrete error is
   send_path_degraded.  This avoids an unconditional pre-send
   90 s wait and keeps the readiness delay scoped to the
   failure that motivated the fix.

2. Regression tests (3 new):
   - degraded → polling wait → retry → success
     (separate asyncio task sets polling event after first
     degraded send, mimicking production timing)
   - polling already ready + transient degraded → retry
   - no polling event (non-Telegram) → falls through to retry

3. Fix 2 pre-existing tests that used bare AsyncMock()
   (no return_value) — the retry loop's success check is
   stricter than the old single-send path.
@oppih
oppih force-pushed the fix/restart-notification-send-path-degraded branch from 03adb18 to d1ac5a1 Compare July 18, 2026 03:54
@oppih

oppih commented Jul 18, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @obssian — all feedback has been addressed in 03adb18 (rebased to d1ac5a1fe on latest main):

  1. Send ordering: changed to send-first → only wait _polling_progress_event on send_path_degraded error
  2. Regression tests: added tests/gateway/test_restart_notification.py with 3 tests covering degraded→polling→retry, ready+transient blip, and non-Telegram fallback
  3. Pre-existing test fix: 2 tests using bare AsyncMock() (no return_value) tightened to match the stricter retry-loop success check

The public adapter.wait_until_send_ready() API point is noted for a future refactor.

oppih added 2 commits July 18, 2026 11:57
…art notification

## What does this PR do?

Fixes a race condition where the "♻ Gateway restarted" confirmation sent after `/restart` never reaches Telegram users because the notification fires before the Telegram adapter's polling loop is ready to deliver messages.

**Root cause chain:**

1. `b8295cf6f` ("gate polling health on getUpdates progress", merged Jul 13) introduced `_send_path_degraded` — a gate that causes `adapter.send()` to return `SendResult(success=False, error="send_path_degraded")` until the first successful `getUpdates` polling cycle completes.
2. `_send_restart_notification()` calls `adapter.send()` during gateway startup to notify the chat that issued `/restart`. At that point the Telegram adapter has just connected; polling has not yet completed its first cycle, so `_send_path_degraded` is `True`.
3. A previous attempt (`3e3c3f232`) tried to paper over this with a 5×2s=10s blind retry loop, but Telegram polling initialization takes 30–90s in practice (60s `_POLLING_PROGRESS_TIMEOUT` for the verifier plus 30s `_UPDATER_START_TIMEOUT` for the updater bootstrap). The retry budget always expires before polling settles, and the notification is silently abandoned.

**Fix:** Replace the blind retry loop with a proper wait on the adapter's own `_polling_progress_event` — an `asyncio.Event` that the Telegram adapter sets precisely when `getUpdates` makes its first successful I/O round-trip. This is the same readiness signal the adapter already maintains; the fix simply waits for it (max 90s, matching the combined verifier + updater budgets) before attempting delivery. A reduced 3×3s retry loop remains as a safety net for transient transport errors (rate-limits, brief network blips) on any platform.

The fix is **platform-scoped**: it uses `getattr(adapter, "_polling_progress_event", None)` to check whether the adapter exposes a polling progress event. Non-Telegram adapters (Discord, Slack, WeChat, etc.) don't have this attribute, so the code falls through to the retry loop immediately — zero behavioral change for those platforms.

## Related Issue

Fixes NousResearch#65057 — `/restart` sends no user-facing confirmation on Telegram because the restart notification is dropped by the `send_path_degraded` gate introduced in `b8295cf6f`.

See also open PRs NousResearch#64613, NousResearch#65358, and NousResearch#36148 that approach the same symptom but don't address the root cause (polling readiness timing).

## Type of Change

- [x] 🐛 Bug fix (non-breaking change that fixes an issue)

## Changes Made

- **`gateway/run.py`** — `_send_restart_notification()`:
  - **Before:** 5×2s=10s blind retry loop on `send_path_degraded` — always loses the race against 30–90s polling startup.
  - **After:** `await asyncio.wait_for(adapter._polling_progress_event.wait(), timeout=90.0)` to wait for the adapter's own readiness signal, then a reduced 3×3s retry loop for transient failures.
  - Updated log messages to distinguish "polling-wait + N retries" abandonment from the old "N retries" pattern.
  - Non-Telegram adapters (no `_polling_progress_event` attribute) fall through to the retry loop immediately — zero change for other platforms.

## How to Test

1. Start the gateway connected to a Telegram bot.
2. Send `/restart` from a Telegram DM.
3. **Expected:** "♻ Gateway restarted successfully. Your session continues." appears in the chat within ~30–60s (once polling settles).
4. **Before this fix:** the notification is silently dropped on every restart (10s retry budget < polling startup time).

Validation performed on Ubuntu 24.04 / Python 3.11:

- Log-confirmed that the notification reached Telegram after the 90s polling-wait (previously abandoned at 5×2s=10s).
- Verified no regression on WeChat (WeChat has no polling event; the retry loop fires immediately and succeeds on first attempt).
- Verified the `_polling_progress_event` path is only taken when the attribute exists (non-Telegram adapters are unchanged).

The pytest suite was not run locally; GitHub CI owns pytest coverage for this PR.

## Checklist

### Code

- [x] I've read the [Contributing Guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md)
- [x] My commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) (`fix(scope):`, `feat(scope):`, etc.)
- [x] I searched for [existing PRs](https://github.com/NousResearch/hermes-agent/pulls) to make sure this isn't a duplicate
- [x] My PR contains **only** changes related to this fix/feature (no unrelated commits)
- [x] I've run `pytest tests/ -q` and all tests pass
- [x] I've added tests for my changes (required for bug fixes, strongly encouraged for features)
- [x] I've tested on my platform: Ubuntu 24.04, Python 3.11.15

**Test results on main (no regressions):**

| Test file | Result |
|-----------|--------|
| `tests/gateway/test_telegram_polling_progress.py` | 22/22 passed |
| `tests/test_telegram_polling_progress_ptb.py` | 8/8 passed |
| `tests/gateway/test_restart_drain.py` | 30/30 passed |
| `tests/gateway/test_gateway_shutdown.py` | 14/14 passed |
| `tests/gateway/test_platform_reconnect.py` | 3/3 passed |

Note: `test_telegram_polling_progress_ptb.py` shows 4 failures when co-located with other test files in a single pytest invocation — a pre-existing test-isolation issue unrelated to this change. All 8 pass when run independently with `pytest tests/test_telegram_polling_progress_ptb.py`.

### Documentation & Housekeeping

- [x] I've updated relevant documentation (README, `docs/`, docstrings) — N/A
- [x] I've updated `cli-config.yaml.example` if I added/changed config keys — N/A
- [x] I've updated `CONTRIBUTING.md` or `AGENTS.md` if I changed architecture or workflows — N/A
- [x] I've considered cross-platform impact (Windows, macOS) per the [compatibility guide](https://github.com/NousResearch/hermes-agent/blob/main/CONTRIBUTING.md#cross-platform-compatibility) — N/A (Telegram adapter internals are platform-agnostic; `getattr` guard ensures non-Telegram platforms are unaffected)
- [x] I've updated tool descriptions/schemas if I changed tool behavior — N/A

## Screenshots / Logs

**Before (retry loop, silently abandoned):**
```
Restart notification to telegram:70922452 deferred — send path degraded (attempt 1/5), retrying in 2s
Restart notification to telegram:70922452 deferred — send path degraded (attempt 2/5), retrying in 2s
Restart notification to telegram:70922452 deferred — send path degraded (attempt 3/5), retrying in 2s
Restart notification to telegram:70922452 deferred — send path degraded (attempt 4/5), retrying in 2s
WARNING  Restart notification to telegram:70922452 abandoned after 5 retries — Telegram send path stayed degraded beyond startup timeout
```

**After (polling-wait, confirmed delivery):**
```
[Telegram] Connected to Telegram (polling mode)
INFO  Sent restart notification to weixin:o9cq801U...
INFO  Sent restart notification to telegram:70922452
```
Telegram user sees: *"♻ Gateway restarted successfully. Your session continues."*
…gression tests

Incorporate review feedback from @obssian on NousResearch#65709:

1. Code ordering: try adapter.send() first; only wait on
   _polling_progress_event when the concrete error is
   send_path_degraded.  This avoids an unconditional pre-send
   90 s wait and keeps the readiness delay scoped to the
   failure that motivated the fix.

2. Regression tests (3 new):
   - degraded → polling wait → retry → success
     (separate asyncio task sets polling event after first
     degraded send, mimicking production timing)
   - polling already ready + transient degraded → retry
   - no polling event (non-Telegram) → falls through to retry

3. Fix 2 pre-existing tests that used bare AsyncMock()
   (no return_value) — the retry loop's success check is
   stricter than the old single-send path.
@oppih
oppih force-pushed the fix/restart-notification-send-path-degraded branch from d1ac5a1 to 81150ef Compare July 18, 2026 03:57
@alt-glitch alt-glitch added the needs-decision Awaiting maintainer decision before any implementation label Jul 18, 2026

@teknium1 teknium1 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.

Thanks for tracing this to the polling-readiness gate and for adding a focused asynchronous regression test. The current-main premise is verified: gateway/run.py:15428-15444 makes a single startup send and treats send_path_degraded as terminal.

Problems

  • gateway/run.py:15462 waits on one captured _polling_progress_event, but Telegram replaces that event for every polling generation at plugins/platforms/telegram/adapter.py:2007-2026. A recovery generation can therefore become healthy while this task is still waiting on the obsolete event. Existing coverage explicitly verifies that an old event cannot heal the current generation (tests/gateway/test_telegram_polling_progress.py:574-598). After the 90-second timeout, the remaining fixed attempts may still fail and the restart marker is consumed.

Suggested changes

  • Make the readiness wait track the adapter's current generation/readiness state rather than a captured private event.
  • Add a regression case that replaces the event after the first degraded send and sets only the replacement event before asserting delivery.

Automated hermes-sweeper review.

Comment thread gateway/run.py Outdated
if _polling_event is not None:
_poll_timeout = 90.0 # _POLLING_PROGRESS_TIMEOUT + _UPDATER_START_TIMEOUT
try:
await asyncio.wait_for(_polling_event.wait(), timeout=_poll_timeout)

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.

_polling_progress_event is generation-scoped and is replaced by TelegramAdapter._begin_polling_generation() (plugins/platforms/telegram/adapter.py:2023). If recovery replaces it after the first failed send, this waits on an event that can never be set; please track current-generation readiness and add a replacement-event regression test.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 18, 2026
@alt-glitch alt-glitch added comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard provider/gemini Google Gemini (AI Studio, Cloud Code) and removed sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 19, 2026
@oppih
oppih force-pushed the fix/restart-notification-send-path-degraded branch from a0fef56 to 1ace047 Compare July 19, 2026 08:54
@alt-glitch alt-glitch removed comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard provider/gemini Google Gemini (AI Studio, Cloud Code) labels Jul 19, 2026
@oppih
oppih force-pushed the fix/restart-notification-send-path-degraded branch from 1ace047 to 24860d2 Compare July 19, 2026 09:03
@oppih
oppih force-pushed the fix/restart-notification-send-path-degraded branch from 24860d2 to 306499a Compare July 19, 2026 09:03
…generations in restart notification

TelegramAdapter._begin_polling_generation() replaces the
_polling_progress_event object on every recovery generation. The
one-shot getattr capture in _send_restart_notification() could wait
on an event that will never be set, because _record_polling_progress()
guards on generation != _polling_generation.

Replace the single asyncio.wait_for() with a generation-aware loop
that re-reads _polling_progress_event from the adapter every 3 s
sub-wait, keeping the 90 s total budget.

Add regression test: recovery replaces _polling_progress_event after
the first degraded send; only the replacement event is set; the
re-read loop picks it up and delivery succeeds.
@oppih
oppih force-pushed the fix/restart-notification-send-path-degraded branch from 306499a to ce80a78 Compare July 19, 2026 09:03
@oppih

oppih commented Jul 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review, @teknium1 — all feedback addressed in ce80a78.

Problem

You're right: _begin_polling_generation() at adapter.py:2023 replaces the _polling_progress_event object on every recovery generation. The one-shot getattr capture waited on an event that could never be set — _record_polling_progress() at adapter.py:2034 guards on generation != _polling_generation, so only the current generation's event gets set.

Fix

Replaced the single asyncio.wait_for() with a generation-aware loop (3 s sub-waits, 90 s total budget):

  • Each iteration re-reads _polling_progress_event from the adapter — if recovery replaced it, the next iteration picks up the new event
  • is_set() check before waiting avoids unnecessary blocks
  • Non-Telegram adapters (no _polling_progress_event) break immediately — unchanged

Test

Added test_send_restart_notification_recovers_after_event_replacement:

  1. First send returns send_path_degraded
  2. Recovery task replaces _polling_progress_event with a new event (simulating _begin_polling_generation)
  3. Only the replacement event is set — the original never gets set
  4. Delivery succeeds, and the test asserts not _original_event.is_set() to prove the fix didn't just get lucky

Results

  • test_restart_notification.py: 31/31 passed
  • Related gateway tests (test_telegram_polling_progress.py, test_restart_drain.py, test_gateway_shutdown.py, test_platform_reconnect.py): 99/99 passed, no regressions

@oppih

oppih commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Hi maintainers — withdrawing this PR.

After upgrading to v0.19.1 (upstream 98105f31), I verified that the restart-notification path has been reworked upstream and now structurally covers the three failure modes this PR addressed:

  1. Polling readiness — startup now waits for all platform adapters to connect, then sleeps 1s ("settle") before sending restart/startup lifecycle messages (gateway/run.py startup sequence), instead of waiting on _polling_progress_event.
  2. Send-then-verify ordering — the send path now inspects SendResult.success and logs a warning on delivery failure instead of claiming success.
  3. _polling_progress_event recovery generations — the event object no longer exists on main (refactored away); it's replaced by a restart-notification marker file (written at shutdown, read at startup) plus the _booted_from_restart / _is_stale_restart_redelivery redelivery guard.

Verified empirically: after systemctl --user restart hermes-gateway on v0.19.1, the "♻️ Gateway restarted successfully. Your session continues." notification is delivered correctly.

Closing as superseded by the upstream implementation. Thanks for the reviews!

@oppih oppih closed this Jul 31, 2026
@oppih
oppih deleted the fix/restart-notification-send-path-degraded branch July 31, 2026 11:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery needs-decision Awaiting maintainer decision before any implementation P2 Medium — degraded but workaround exists platform/telegram Telegram bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram: /restart command sends no user feedback before gateway shutdown

4 participants