hermes-agent/tests/plugins
Spark (DGX) 3c7cff843d fix(photon): dispatch fatal-error notification from a detached task
Follow-up to #69112. That PR hardened the shared gateway dispatch path in
gateway/run.py against the *caller* being cancelled. A second, self-referential
cancellation specific to PhotonAdapter sits one layer underneath it and survived
that fix.

PhotonAdapter is the only platform adapter that awaits _notify_fatal_error()
inline, on the same task that detected the fault. Both _monitor_sidecar_health
and _supervise_sidecar run as self._sidecar_health_task /
self._sidecar_supervisor_task, and the notification routes into
GatewayRunner._handle_adapter_fatal_error_impl, which tears the adapter down via
_safe_adapter_disconnect -> disconnect(). disconnect() then cancels
self._sidecar_health_task and awaits it -- which, when the health task is what
raised the notification, means disconnect() cancels its own caller several
plain-await frames up.

disconnect()'s `task is not asyncio.current_task()` guard does not catch this.
The current task where that guard evaluates is the wrapper
_await_adapter_cleanup_with_timeout creates around disconnect() via
asyncio.ensure_future, not the health task further up the chain, so the guard
passes and the cancel lands.

CancelledError stopped subclassing Exception in Python 3.8, so the
`except Exception` that wrapped the inline notify call never saw it. The health
task died silently mid-handoff: no log line, no "exception never retrieved"
warning (cancellation is normal asyncio), and no retry. The platform stayed
stranded until the gateway was restarted by hand.

Fix: dispatch the notification onto a new task, the same pattern
DiscordAdapter._handle_bot_task_done already uses for this reason. disconnect()
can then cancel the health/supervisor task freely without that cancellation
reaching the code still running the handoff, so the handoff always reaches the
reconnect queue. Both Photon fatal call sites are converted: the health-poll
path (observed wedging) and the sidecar-crash path (same shape, not yet
observed). gateway/run.py is untouched.

Observed twice on a self-hosted gateway, ~4h38m and ~52min of silent inbound
outage, both cleared only by a manual restart, both post-dating #69112's merge.
In each case the fatal log line appears and `queued for background reconnection`
never does.

Tests: new tests/plugins/platforms/photon/test_fatal_notify_self_cancel.py
covers the self-cancellation (fails with CancelledError without this change),
that the dispatch does not block its caller, that a failing notification warns
rather than raising, and a source guard against reintroducing either inline
await. Two assertions in test_overflow_recovery.py that drove these coroutines
directly now drain pending tasks before asserting delivery, since the
notification is deliberately no longer awaited inline.

Prepared with agent assistance and reviewed before submission.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 21:45:52 -07:00
..
browser
dashboard_auth feat(dashboard_auth): support confidential clients (client_secret) in self-hosted OIDC (#55344) 2026-06-30 13:32:51 +10:00
image_gen fix(image-gen): stop reporting the Codex tool_choice 400 as an account limit 2026-07-25 16:41:57 -07:00
memory fix(memory): cover the remaining setup-time .env reads with utf-8-sig 2026-07-24 17:10:39 -07:00
model_providers fix(gemini): bump native provider aux default to gemini-3.6-flash 2026-07-28 11:54:46 -07:00
platforms/photon fix(photon): dispatch fatal-error notification from a detached task 2026-07-28 21:45:52 -07:00
transcription feat(stt): add stt.providers.<name> command-provider registry 2026-05-25 01:41:19 -07:00
tts feat(tts): add register_tts_provider() plugin hook (closes #30398) 2026-05-24 18:04:54 -07:00
video_gen fix(video-gen): omit duration for range-based FAL families when unspecified 2026-07-16 04:24:10 -07:00
web revert(web): remove keyless Parallel search fallback (#46350) 2026-06-14 16:47:57 -07:00
__init__.py
test_achievements_plugin.py test: use subprocesses for each test file (#29016) 2026-05-21 16:40:04 +05:30
test_chronos_cron.py fix(cron): avoid provider package shadowing core cron 2026-06-23 23:39:22 -07:00
test_chronos_verify.py fix(cron/chronos): cache PyJWKClient across fires to stop JWKS fetch storm (#64641) 2026-07-15 09:27:35 +10:00
test_discord_runtime_failure.py fix(discord): recover from runtime gateway task exits (#44383) 2026-06-11 15:39:01 -04:00
test_disk_cleanup_plugin.py fix: protect cron output root from cleanup 2026-07-01 15:42:04 +05:30
test_google_meet_audio.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_google_meet_node.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_google_meet_plugin.py test(google_meet): assert ladder-based dependency install instead of bespoke pip argv 2026-07-07 04:09:35 -07:00
test_google_meet_realtime.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_hindsight_health_grace_timeout.py feat(hindsight): configurable embedded daemon health grace timeout (#50341) 2026-06-21 12:20:53 -07:00
test_hindsight_root_guard.py fix(hindsight): skip local_embedded daemon when running as root 2026-06-21 11:47:02 -07:00
test_kanban_attachments.py feat(kanban): attachment toolset + CLI to match the dashboard surface 2026-07-16 07:33:14 -07:00
test_kanban_dashboard_plugin.py fix(dashboard): one gateway liveness ladder for status + channels 2026-07-26 08:08:38 -07:00
test_kanban_model_override.py feat(kanban): per-task model dropdown — set/override worker model+provider from the board (#69876) 2026-07-22 22:23:24 -07:00
test_kanban_worker_runs.py feat(kanban): add POST /runs/{run_id}/terminate endpoint 2026-05-29 00:21:54 -07:00
test_langfuse_plugin.py test(langfuse): pin exact surviving key in turn-isolation test 2026-06-18 13:00:01 +05:30
test_nemo_relay_plugin.py Reapply "feat(observability): integrate NeMo Relay runtime and shared metrics" 2026-07-27 21:10:51 -07:00
test_plugin_dashboard_auth_contract.py fix(dashboard): sanction plugin WS/upload auth via SDK helpers (gated mode) 2026-06-03 16:59:36 -07:00
test_raft_check_fn_silent.py fix(plugins): silence raft check_fn log spam for users without raft CLI 2026-06-19 17:12:58 -07:00
test_retaindb_plugin.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_security_guidance_plugin.py chore: prune unused imports and duplicate import redefinitions 2026-05-28 22:26:25 -07:00
test_teams_pipeline_plugin.py fix(teams-pipeline): reject dot-only recording display_name 2026-07-01 02:03:48 -07:00