fix(polling): require consecutive misses before declaring a window dead - #131
Open
oliver-mee wants to merge 1 commit into
Open
fix(polling): require consecutive misses before declaring a window dead#131oliver-mee wants to merge 1 commit into
oliver-mee wants to merge 1 commit into
Conversation
status_poll_loop rebuilds its window lookup each cycle from TmuxManager.list_windows(), which returns an empty list when get_session() hits a transient _TmuxError and silently drops individual windows whose pane metadata raises. tick_window treated a single absent poll as death and fired the recovery banner, so one bad read could kill a live session's topic. The is_dead_notified guard muted already-notified topics, which made it look like only freshly created sessions were affected. Track consecutive misses per window on WindowPollState and only take the dead path once MAX_MISSING_POLLS cycles in a row have missed, mirroring the existing MAX_PROBE_FAILURES debounce. A window that reappears resets the counter. Genuine deaths still surface instantly through the window_died event stream, which does not go through this path.
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
status_poll_looprebuilds its window lookup every cycle fromTmuxManager.list_windows().That method returns an empty list when
get_session()hits a transient_TmuxError(
multiplexer/tmux.py:206) and silently drops individual windows whose pane metadata raises(
multiplexer/tmux.py:250,logger.debugonly).tick_windowtreated a single absent poll asdeath and went straight to
_handle_dead_window_notification, so one bad read could put a⚠ Session ... ended.banner on a topic whose window and agent were both still alive.The
is_dead_notifiedguard muted topics that had already been notified, so on a bad tick theonly topic that visibly got a banner was the most recently created one. That is why it presents
as "only new sessions disconnect".
This adds a
missing_pollscounter toWindowPollStateand only takes the dead path onceMAX_MISSING_POLLSconsecutive cycles have missed the window. A window that reappears resetsthe counter. It mirrors the
MAX_PROBE_FAILURESdebounce already used for topic probes, so theshape should be familiar.
Genuine deaths are unaffected: the
window_diedevent stream calls_handle_dead_window_notificationdirectly and does not route through this path, so it stillfires instantly.
At the default 1s poll interval this delays a real tmux-backend death banner by about 2 seconds.
I kept the change out of
polling_coordinator.pydeliberately. It is already at its 120 lineceiling, and a module-level counter there would have needed state the coordinator is not
supposed to own.
Linked issue
Fixes #130
Type of change
How to test
uv run pytest tests/ccgram/handlers/polling/test_window_tick.py -k Missing -vcovers the newbehaviour: a single miss and a sub-threshold run of misses do not notify, a window that
reappears resets the counter, and nothing fires before the threshold is crossed.
TestTickWindowDeadWindowcases now tickMAX_MISSING_POLLStimes beforeasserting, since one tick is no longer enough to declare death. That is the intended
behaviour change.
TmuxManager.list_windowsto return[]on a single callwhile a bound window is alive. On
mainthat produces a recovery banner on a live topic; withthis change it does not.
Ran locally on Python 3.14:
ruff format --check,ruff check,pyright src/ccgram/,deptry srcall clean. Unit suite 5931 passed, integration suite 304 passed. Three failures intest_status_polling.py::TestMaybeDiscoverTranscriptalso fail on a clean checkout ofmainat a475e0f (a
KeyError: 'shell'in a provider registry mock), so they are unrelated to thischange.
Checklist
I hit this in daily use on ccgram 4.3.11 and have been running the equivalent debounce on my own
box since, with no recurrence of the spurious banner and no delay I have noticed on real window
deaths.
Model used
Claude Opus 4.8 via Claude Code. It did the code reading, wrote the patch and the tests, and ran
the suites. I found the bug in daily use, directed the investigation, chose to put the debounce
in
window_tickrather than the coordinator, and reviewed the result.🤖 Generated with Claude Code (Claude Opus 4.8)
🧑💻 Ideated, directed and reviewed by a human, @oliver-mee