HERMES-GIELIE/api
nesquena-hermes e68f74ac99 fix(approval): close SSE notify-ordering, head-fidelity, and trailing-approval gaps (Opus MUST-FIX A/C/D)
Pre-release Opus review caught three correctness bugs in the original
PR #1350 SSE wiring beyond the snapshot/subscribe race:

A) **Notify-ordering race (MUST-FIX A):** _approval_sse_notify took _lock
   only for the subscriber-list snapshot, then released it before
   put_nowait. With two parallel submit_pending calls, T2's notify
   could fire before T1's, leaving the UI showing pending_count=1 while
   the server actually had 2 queued.

C) **Trailing approval lost (MUST-FIX C):** _handle_approval_respond
   never called _approval_sse_notify after popping. With parallel
   tool-call approvals (#527), a second approval queued behind the one
   being responded to was invisible until the next event ever fired —
   in practice, the agent thread parked on it would appear hung.

D) **Payload showed tail not head (MUST-FIX D):** payload built from
   the just-appended entry instead of queue[0]. /api/approval/pending
   returns the head; SSE returned the tail. Diverging contracts.

Fix:
- Split into _approval_sse_notify_locked (caller holds _lock, no
  internal locking) and _approval_sse_notify (convenience wrapper).
- submit_pending: call _locked variant inside the queue-mutation lock,
  passing queue_list[0] as head.
- _handle_approval_respond: call _locked variant inside the pop lock,
  passing the new head (or None/0 if queue is empty).
- Restore fallback poll to 1500ms (was bumped to 3000ms; degraded-mode
  parity with v0.50.247 is more important than save 1.5s of polling).

New regression tests in tests/test_pr1350_sse_notify_correctness.py:
- test_second_submit_pending_sends_head_not_tail (D)
- test_respond_to_first_pushes_second_as_new_head (C)
- test_respond_to_only_pending_pushes_empty_state (C edge)
- test_pending_count_is_monotonic_under_contention (A)

Updated test_approval_sse.py to pin the new contract:
- _approval_sse_notify_locked(session_key, head, total)
- 1500ms fallback interval

Total: 3411 tests passing.

Co-authored-by: jasonjcwu <jasonjcwu@users.noreply.github.com>
2026-04-30 18:45:15 +00:00
..
__init__.py Hermes Web UI — Sprints 11-14: multi-provider models, settings, session QoL, alerts, polish 2026-03-31 07:02:47 +00:00
agent_sessions.py release: v0.50.241 (#1293) 2026-04-29 19:54:07 -07:00
auth.py fix(auth): persist sessions across restarts via STATE_DIR/.sessions.json (#962) 2026-04-24 11:21:41 -07:00
background.py feat(commands): /background, /btw slash commands + undo button + reasoning chip 2026-04-24 01:24:51 +00:00
clarify.py fix: preserve clarify drafts on timeout 2026-04-29 04:32:40 +00:00
commands.py feat: slash command parity + skill autocomplete — v0.50.91 (PR #711) 2026-04-19 05:37:44 +00:00
config.py fix(models): default model rehydration when providers share slash-qualified IDs (#1313) 2026-04-30 15:24:35 +00:00
gateway_watcher.py release: v0.50.241 (#1293) 2026-04-29 19:54:07 -07:00
helpers.py v0.50.223: model picker, idle retry, drag-drop, CSP, clipboard copy (#1127) 2026-04-26 15:29:02 -07:00
metering.py v0.50.207: batch of 10 PRs — TPS stat, SSE guard, session polish, cron UX, folder create, model errors, session speed, title gen (#1031) 2026-04-25 13:07:35 -07:00
models.py perf(cron): memoize ensure_cron_project() per get_cli_sessions() scan 2026-04-30 17:21:51 +00:00
onboarding.py absorb: address Opus review findings (security + correctness) 2026-04-29 05:06:34 +00:00
profiles.py fix: apply profile terminal env in webui sessions 2026-04-29 14:12:59 +08:00
providers.py Fix MiniMax China provider visibility 2026-04-29 15:50:32 +08:00
routes.py fix(approval): close SSE notify-ordering, head-fidelity, and trailing-approval gaps (Opus MUST-FIX A/C/D) 2026-04-30 18:45:15 +00:00
session_ops.py Preserve transcript across context compaction 2026-04-29 16:37:08 +08:00
startup.py fix(security): gate auto-install behind HERMES_WEBUI_AUTO_INSTALL=1 — v0.50.156 2026-04-22 20:49:28 +00:00
state_sync.py security: bandit fixes B310/B324/B110 + QuietHTTPServer (#354) 2026-04-13 11:11:56 -07:00
streaming.py fix(streaming): fallback to model_metadata for context_length when compressor missing (#1318 follow-up) (#1348) 2026-04-30 10:27:56 -07:00
terminal.py absorb: address Opus review findings (security + correctness) 2026-04-29 05:06:34 +00:00
updates.py fix: update banner — conflict recovery path + server self-restart after update (#816) 2026-04-21 17:10:41 -07:00
upload.py Merge remote-tracking branch 'refs/remotes/pr/1229' into stage/batch-v0.50.238 2026-04-29 15:17:57 +00:00
workspace.py fix: batch v0.50.234-235 — XSS hardening, workspace validation, profile switch fixes (#1206) 2026-04-27 21:39:30 -07:00