Repository navigation
Conversation
Textual's App._shutdown clears _running, removes every screen and widget, and only then sends the App its Unmount, which is where KorvidApp marks the agent session down. A turn the user interrupted just before quitting can settle in between. _finish_interrupted_turn then still sees _shutting_down as False and hands its TurnInterrupted to the panel port, which looks the panel up on a pruned screen. The resulting NoMatches fails the turn task and escapes app shutdown through wait_for_turn (#476). On #471's CI it surfaced as "No nodes match '#agent-status' on AgentPanel()". AppAgentPanel.apply_event, the one place agent events reach the panel, now drops an event once the app is no longer running. The new test reproduces the ordering deterministically: the interrupted turn's cleanup waits for the panel's Unmount, so it settles after Textual pruned the panel and before the App's Unmount. Without the guard it fails with NoMatches every run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
No blocking code issues were identified, but completion of the required Python-version and Windows CI checks remains unconfirmed.
No open issues 🎉
What changed in this PR
Prevents late agent events from updating dismantled UI widgets during shutdown, addressing the NoMatches failure reported in #476.
Changes:
- Drops agent panel events once Textual marks the app as no longer running, without changing session cleanup.
- Adds a regression test that makes an interrupted turn settle after the panel is pruned.
| File | Description |
|---|---|
tests/ui/test_agent_interrupt.py |
Adds coverage for an interrupted turn settling during UI teardown. |
src/korvid/ui/app_surfaces.py |
Guards panel event delivery with the app’s running state. |
Review effort: Balanced
5 tasks done
5 tasks done
Owner
Author
|
Superseded by #482, which carries this pull request's commits unchanged (d64ad2b) together with the other open fixes and the dependency security bumps, so all of them need one CI run and one merge instead of a rebase after each merge. Closing in favour of #482; the branch stays, so this can be reopened if needed. |
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.
Closes #476
Summary
test_stop_then_immediate_shutdown_injects_cancellation_oncefailed ontest (3.12)in #471's CI withNoMatches: No nodes match '#agent-status' on AgentPanel(), raised out ofKorvidApp.on_unmount.Textual's
App._shutdowndoes three things in order:_running._close_all).Unmount.Unmountis whereKorvidAppcallsbegin_shutdown. A turn the user interrupted just before quitting can settle inside step 2._finish_interrupted_turnthen still sees_shutting_downasFalseand handsTurnInterruptedto the panel port, which looks the panel up on a pruned screen. TheNoMatchesfails the turn task,shutdown()re-raises it fromwait_for_turn, and it escapes app shutdown. A user could hit this by stopping a turn and quitting at once. A loaded runner widens the window.AppAgentPanel.apply_event, the one place agent events reach the panel, now drops an event once the app is no longer running. Textual clears that flag before it removes anything, so the guard covers both orderings: the panel already gone, and the panel left with no children._shutting_downbookkeeping. The controller'sFakePanelintest_agent_ui_controller.pydoesn't go through this adapter. This is the same family as DeploymentOutcomeScreen.on_mount can raise NoMatches when pushed during app shutdown #455/fix: tolerate the outcome screen mounting during shutdown #456, where a screen was mounted during shutdown.Verification
tests/ui/test_agent_interrupt.py::test_a_turn_that_settles_after_the_panel_is_pruned_does_not_raisereproduces the ordering deterministically. The interrupted turn's cleanup waits for the panel'sUnmount, so it settles after Textual has pruned the panel and before the App'sUnmount.NoMatches.An earlier version that polled for the pruned children every 10 ms passed even without the fix. By the time it noticed,
begin_shutdownhad already run, which is why the final test waits on the panel'sUnmountitself.Run locally on macOS with Python 3.13 and the frozen lock. Each step ran directly with the project's environment rather than through
make, because this machine cannot sync from PyPI:pytest -p no:tach tests/ui/test_agent_interrupt.py tests/ui/test_agent_ui_controller.py tests/ui/test_app_structure.py tests/ui/test_agent_wiring.pypython scripts/check_source_size.pyruff check src/ tests/andruff format --check src/ tests/SKIP=mypy,source-size pre-commit run --files <changed files>mypytach checkpytest -n 8 -qThis PR's CI (run 39992085249, hosted runners, four xdist workers) passed every required check:
test (3.11)test (3.12)test (3.13)windows-testChecklist
make checkpasses locally.and a fail-closed audit entry, and sensitive reads are still masked.