Skip to content

fix(tui): defer _reload_projects to survive pytest-xdist race#121

Merged
aorumbayev merged 1 commit intomainfrom
fix/ci-welcome-query-one-race
Apr 23, 2026
Merged

fix(tui): defer _reload_projects to survive pytest-xdist race#121
aorumbayev merged 1 commit intomainfrom
fix/ci-welcome-query-one-race

Conversation

@aorumbayev
Copy link
Copy Markdown
Member

Summary

  • CI has been red on every push event for days — root cause is a query_one race in WelcomeScreen.on_mount that manifests under pytest-xdist parallel workers.
  • Latest CI failure (run 24829443418 on 45335d1): test_doctor_modal_skip_button_dismisses_modal failed with No nodes match '#project-list' on WelcomeScreen(id='welcome-screen')on_mount fires before the composed children are queryable.
  • Fix: wrap the on_mount body in call_after_refresh(self._on_mount_deferred) so reload runs after the DOM is stable. Textual's idiomatic defer pattern.

Test plan

  • 5× parallel runs of the specific failing test under pytest -n auto — all pass.
  • Full tests/tui/ -m "not snapshot" -n auto — 129/129 pass.
  • Sequential tests/tui/test_doctor_modal.py — 16/16 pass (no regression in non-parallel mode).
  • CI matrix green on this PR.

Known follow-ups (not in this PR)

  • WelcomeScreen.on_screen_resume has the same pattern — lower risk but worth deferring for consistency.
  • _update_cwd_banner_hint and _cwd_banner_visible both call query_one without guard — could race during early resumes.
  • Other files show the same pattern (session_resume_modal.py, kanban.py etc) — systematic audit needed in a follow-up.

🤖 Generated with Claude Code

…st race

on_mount called _reload_projects synchronously, which immediately ran
query_one('#project-list') before compose() children were fully in the
DOM under pytest-xdist parallel workers. Defer via call_after_refresh so
the whole mount sequence runs after the first layout pass.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@aorumbayev aorumbayev requested a review from kagan-agent as a code owner April 23, 2026 10:42
@aorumbayev aorumbayev merged commit 8bd88cb into main Apr 23, 2026
20 checks passed
@aorumbayev aorumbayev deleted the fix/ci-welcome-query-one-race branch April 23, 2026 10:48
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 23, 2026

Greptile Summary

Wraps the on_mount body in call_after_refresh so _reload_projects and all query_one calls run after the DOM is stable — the correct Textual idiom for deferring mount-time work. The change is minimal and directly addresses the pytest-xdist race that caused CI failures.

Confidence Score: 5/5

Safe to merge — the fix is correct and the only finding is a minor style point.

The change correctly uses Textual's call_after_refresh idiom to defer DOM access; no P0/P1 issues identified. The sole comment is a P2 style suggestion (removing the now-unnecessary async from on_mount).

No files require special attention.

Important Files Changed

Filename Overview
src/kagan/tui/screens/welcome.py Defers _reload_projects and DOM queries from on_mount to call_after_refresh, fixing the pytest-xdist race. on_screen_resume still queries the DOM directly but is a known follow-up.

Sequence Diagram

sequenceDiagram
    participant Textual
    participant WelcomeScreen
    participant DOM

    Note over Textual,DOM: Before fix
    Textual->>WelcomeScreen: on_mount()
    WelcomeScreen->>DOM: query_one('#project-list') ❌ DOM not ready

    Note over Textual,DOM: After fix
    Textual->>WelcomeScreen: on_mount()
    WelcomeScreen->>Textual: call_after_refresh(_on_mount_deferred)
    Textual->>DOM: first refresh / layout pass
    Textual->>WelcomeScreen: _on_mount_deferred()
    WelcomeScreen->>DOM: query_one('#project-list') ✅ DOM stable
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/kagan/tui/screens/welcome.py
Line: 99

Comment:
**Unnecessary `async` on `on_mount`**

`on_mount` no longer awaits anything — it only calls the synchronous `call_after_refresh`. Declaring it `async def` carries a tiny overhead and can be misleading; a plain `def` is cleaner and equally correct here.

```suggestion
    def on_mount(self) -> None:
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(tui): defer _reload_projects to call..." | Re-trigger Greptile

@@ -97,6 +97,9 @@ def compose(self) -> ComposeResult:
yield KeybindingHint(id="welcome-hint")

async def on_mount(self) -> None:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unnecessary async on on_mount

on_mount no longer awaits anything — it only calls the synchronous call_after_refresh. Declaring it async def carries a tiny overhead and can be misleading; a plain def is cleaner and equally correct here.

Suggested change
async def on_mount(self) -> None:
def on_mount(self) -> None:
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/kagan/tui/screens/welcome.py
Line: 99

Comment:
**Unnecessary `async` on `on_mount`**

`on_mount` no longer awaits anything — it only calls the synchronous `call_after_refresh`. Declaring it `async def` carries a tiny overhead and can be misleading; a plain `def` is cleaner and equally correct here.

```suggestion
    def on_mount(self) -> None:
```

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

aorumbayev added a commit that referenced this pull request Apr 23, 2026
Since _reload_projects was deferred into _on_mount_deferred, on_mount no
longer awaits anything — just calls synchronous call_after_refresh.
Dropping async makes the signature clearer. Addresses Greptile comment
on PR #121.
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