fix(process): keep poll read-only so notify_on_complete watcher fires (#10156) - #50224
Merged
Conversation
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
Contributor
🔎 Lint report:
|
| 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.
Collaborator
This was referenced Jun 21, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A read-only
process(action='poll')on an exited background process no longer silently kills thenotify_on_completewatcher's delivery turn (#10156), while the CLI still avoids the duplicate[SYSTEM: ...]injection that #8228 fixed.Root cause: PR #8228 added
_completion_consumedto suppress duplicate completion notifications and applied it topoll()as well aswait()/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 checkis_completion_consumed, so a read-only poll never suppresses their delivery turn._poll_observedset thatpoll()populates on an observed exit.drain_notifications()(CLI only) skips poll-observed sessions; the watchers deliberately do not consult it. Pruned alongside_completion_consumed.Validation
[SYSTEM:]injectionE2E verified with a real subprocess:
poll()records_poll_observed, leavesis_completion_consumedFalse → CLIdrain_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