Skip to content

fix(process): keep poll read-only so notify_on_complete watcher fires (#10156) - #50224

Merged
teknium1 merged 3 commits into
mainfrom
hermes/hermes-45ebaeda
Jun 21, 2026
Merged

fix(process): keep poll read-only so notify_on_complete watcher fires (#10156)#50224
teknium1 merged 3 commits into
mainfrom
hermes/hermes-45ebaeda

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

A read-only process(action='poll') on an exited background process no longer silently kills the notify_on_complete watcher's delivery turn (#10156), while the CLI still avoids the duplicate [SYSTEM: ...] injection that #8228 fixed.

Root cause: PR #8228 added _completion_consumed to suppress duplicate completion notifications and applied it to poll() as well as wait()/read_log(). That was correct for the CLI (the poll result is returned inline, so a queued completion would be a duplicate) but wrong for the gateway/tui watcher, whose synthetic notification exists to spawn an autonomous turn that delivers the result to the user. Polling for status ≠ delivering, so a status check permanently suppressed delivery.

Changes

  • tools/process_registry.py: poll() no longer marks _completion_consumed (cherry-picked from @de1tydev / fix: keep poll read-only for notify_on_complete watcher #10158). The gateway (gateway/run.py) and tui watchers only check is_completion_consumed, so a read-only poll never suppresses their delivery turn.
  • Follow-up (keeps fix: suppress duplicate completion notifications when agent consumes output via wait/poll/log #8228 intact for the CLI): added a separate _poll_observed set that poll() populates on an observed exit. drain_notifications() (CLI only) skips poll-observed sessions; the watchers deliberately do not consult it. Pruned alongside _completion_consumed.
  • Tests: cherry-picked gateway regression test + 4 new registry tests (CLI drain dedup after poll, gateway gate untouched, running poll doesn't mark observed, wait/log still skip CLI drain).

Validation

Scenario Before After
Gateway: agent polls finished bg process, then turn ends watcher suppressed → user never notified watcher delivers autonomous turn
CLI: agent polls finished bg process inline (one notification) no duplicate [SYSTEM:] injection
Process never polled by agent (CLI) 1 notification 1 notification (unchanged)

E2E verified with a real subprocess: poll() records _poll_observed, leaves is_completion_consumed False → CLI drain_notifications() returns 0 while the gateway gate stays open. Targeted suites green: test_notify_on_complete.py, test_process_registry.py, test_internal_event_bypass_pairing.py, test_tui_gateway_server.py.

Salvages #10158 by @de1tydev (authorship preserved via rebase-merge). Closes #10156.

Infographic

poll-stays-read-only-watcher-fires

de1tydev and others added 3 commits June 21, 2026 08:31
Follow-up to @de1tydev's poll-read-only fix. Removing the
_completion_consumed.add() from poll() fixes the gateway/tui watcher
suppression (#10156) but reintroduces the CLI duplicate that #8228 fixed:
a notify_on_complete process always enqueues a completion event, and the
CLI idle/post-turn drain would re-inject it as a [SYSTEM: ...] message
even though the agent already saw the exit inline in its poll result.

Add a separate _poll_observed set that poll() populates on an observed
exit. drain_notifications() (CLI only) skips poll-observed sessions; the
gateway/tui watchers keep checking only is_completion_consumed, so a
read-only poll never suppresses their autonomous delivery turn.

- _poll_observed pruned alongside _completion_consumed in _prune_if_needed
- 4 tests: CLI drain dedup after poll, gateway gate untouched, running
  poll doesn't mark observed, wait/log still skip CLI drain
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-45ebaeda vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11306 on HEAD, 11302 on base (🆕 +4)

🆕 New issues (2):

Rule Count
unresolved-attribute 2
First entries
run_agent.py:2984: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`

✅ Fixed issues (1):

Rule Count
invalid-assignment 1
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`

Unchanged: 5923 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/bug Something isn't working comp/tools Tool registry, model_tools, toolsets tool/terminal Terminal execution and process management comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists labels Jun 21, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related: #10158 (original by @de1tydev, salvaged here) and #10156 (the notify_on_complete watcher-suppression bug this fixes). Keeps #8228's CLI dedup intact via the separate _poll_observed set.

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 comp/tools Tool registry, model_tools, toolsets P2 Medium — degraded but workaround exists tool/terminal Terminal execution and process management type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression: poll() marking _completion_consumed silently suppresses watcher notification

3 participants