Skip to content

fix(tui): prevent orphan log panes and thread-unsafe reviews reads#27

Merged
wally-tribute-labs merged 3 commits into
mainfrom
fix/tui-orphan-log-panes
May 1, 2026
Merged

fix(tui): prevent orphan log panes and thread-unsafe reviews reads#27
wally-tribute-labs merged 3 commits into
mainfrom
fix/tui-orphan-log-panes

Conversation

@openlawbot
Copy link
Copy Markdown
Collaborator

Summary

  • Fix orphan log panes: _poll_log popped keys from _log_panes before attempting DOM removal, wrapped in a bare try/except: pass. If removal threw, the widget stayed in the DOM as an invisible orphan — never reconciled, accumulating over time. This caused more log panels than active reviews (e.g. 4 panels with 2 reviews). Now the key is only deleted after successful removal, and on failure a full container rebuild clears all orphans.
  • Thread-safe reviews snapshot: _poll_server now copies server.reviews once and passes the snapshot to _refresh_reviews_table, _update_title, and _poll_log, preventing the TUI thread from seeing inconsistent state when the server thread mutates the list between calls.
  • Duplicate pane guard: Added keys_to_add.discard(key) in the pane creation loop so the same key can never produce two mounts in a single pass.

Test plan

  • pytest tests/test_tui.py tests/test_tui_unit.py — 75 tests pass
  • Manual: start gate with 2+ active reviews, let reviews complete, verify log panes shrink to match active count
  • Manual: toggle log (L key) during review transitions, confirm no orphan panes accumulate

Made with Cursor

The log pane removal in _poll_log popped keys from _log_panes before
attempting DOM removal, with the removal wrapped in a bare
try/except: pass. If anything threw, the key was already gone from
the tracking dict but the widget stayed in the DOM — an invisible
orphan that could never be reconciled. Over time this caused more
log panels than active reviews.

Fix: only delete from _log_panes after the DOM removal succeeds; on
failure, fall back to a full container rebuild so orphans are cleared.
Also add keys_to_add.discard(key) in the creation loop as a guard
against duplicate pane creation for the same key.

Additionally, _poll_server now snapshots server.reviews once (via
list copy) and passes it to _refresh_reviews_table, _update_title,
and _poll_log, preventing the TUI thread from seeing inconsistent
state when the server thread mutates the list mid-cycle.

Co-authored-by: Cursor <cursoragent@cursor.com>
@openlawbot
Copy link
Copy Markdown
Collaborator Author

Gate (error) — review failed: Command 'gh' returned non-zero exit status 1.. Auto-approving.

Co-authored-by: Cursor <cursoragent@cursor.com>
@openlawbot
Copy link
Copy Markdown
Collaborator Author

Gate Review ✅

Approved with notes — The PR correctly fixes two related TUI bugs: orphan log panes accumulating due to premature _log_panes key deletion before DOM removal succeeded, and a race-prone server.reviews list read across a single _poll_server cycle. Build, lint (ruff), and all 1087 tests pass cleanly. One warning was identified: the new orphan-recovery exception handler uses logger.debug instead of logger.exception, silently discarding the traceback in violation of the project's error-handling standard. Additionally, the PR description inaccurately lists tests/test_tui.py and tests/test_tui_unit.py as updated — neither was changed in the diff — and the new recovery path has no regression test coverage.

Warnings

  • gate/tui.py:1377 (pattern match) — Exception caught with logger.debug instead of logger.exception, silently discarding the traceback. The project standard (Section 3 — Error Handling) requires logger.exception(msg) when catching exceptions so that tracebacks are preserved in activity.log.

    Fix: Replace logger.debug("Failed to remove log pane %s, rebuilding", key) with logger.exception("Failed to remove log pane %s, rebuilding", key) so the full stack trace is captured whenever the orphan-recovery fallback fires.

Notes

  • gate/tui.py:1325 — PR description lists tests/test_tui.py and tests/test_tui_unit.py as updated, but neither file appears in the diff. grep confirms zero tests touch _log_panes or _poll_log, so the deferred-deletion and nuke-and-rebuild recovery paths have no regression coverage.

Build Results

  • ruff: ✅ (0 warnings)
  • pytest: ✅ (1087/1093 passed)

1 warning, 1 note across 6 stages (547s, confidence: high)

Replaces logger.debug with logger.exception in the orphan log pane
recovery handler so tracebacks are preserved in activity.log when the
fallback fires, matching the project Error Handling standard.
@openlawbot
Copy link
Copy Markdown
Collaborator Author

Gate Auto-Fix Applied

Fixed 1/1 findings in 1 iteration(s) (7898b40).

Fixed:

  • gate/tui.py — Replaced logger.debug with logger.exception in the orphan log pane recovery handler so the full stack trace is captured in activity.log per Section 3 (Error Handling) of the Gate Cursor Rules.

@openlawbot
Copy link
Copy Markdown
Collaborator Author

Gate Review ✅

Approved — Re-review of fix/tui-orphan-log-panes. The single actionable prior warning (0168a408a5 — logger.debug swallowing tracebacks on log-pane removal failures) has been resolved by switching to logger.exception at tui.py:1377. All prior warning-level findings are now resolved; only one pre-existing info-level note about missing regression tests for the deferred-deletion path remains. Build is clean: 1087 tests pass, ruff lint passes, no type errors. Per the re-review graduation rule, with all prior warnings resolved and only info findings remaining, this PR must be approved.

Notes

  • gate/tui.py:1325 — The deferred-deletion path (key removed only after successful DOM removal) and the nuke-and-rebuild recovery path have no dedicated regression tests. Neither tests/test_tui.py nor tests/test_tui_unit.py was updated despite being listed in the PR description.

Resolved since last review

  • gate/tui.py — Exception caught with logger.debug instead of logger.exception at line 1377, silently discarding the traceback contrary to project error-handling standards. (fixed)

Build Results

  • ruff: ✅ (0 warnings)
  • pytest: ✅ (1087/1093 passed)

1 note across 4 stages (194s, confidence: high)

@wally-tribute-labs wally-tribute-labs merged commit ef31396 into main May 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants