Skip to content

Comments

fix: post-merge fixes for #802 (tests, StdioBridge flakiness, focus nudge)#804

Merged
dsarno merged 5 commits intoCoplayDev:betafrom
dsarno:fix/802-review-feedback
Feb 21, 2026
Merged

fix: post-merge fixes for #802 (tests, StdioBridge flakiness, focus nudge)#804
dsarno merged 5 commits intoCoplayDev:betafrom
dsarno:fix/802-review-feedback

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Feb 21, 2026

Summary

Post-merge fixes for #802 that weren't included in the squash merge, plus two related issues discovered during test validation.

  • Prefab test fix: Remove broken root rename assertion from CombinesWithOtherModificationsLoadAssetAtPath returns filename as .name for prefab roots, so the assertion always failed
  • StdioBridge reconnect test fix: Remove 5-frame yield that created a window for the MCP Python server to reconnect and close the test client as stale. Stale-client cleanup runs synchronously in HandleClientAsync, so no yield is needed
  • Focus nudge logging fix: Add file handler to root logger so __name__-based loggers (utils.focus_nudge, services.tools.run_tests) write to the log file instead of only stderr
  • Focus nudge fire-and-forget: Add nudge check on non-wait_timeout get_test_job calls so stalls are detected regardless of client polling style
  • 13 new unit tests for should_nudge() logic, backoff reset, and nudge_unity_focus() gating

Test plan

  • 724 Python tests pass (711 existing + 13 new focus nudge tests)
  • 674 EditMode tests pass (630 + 44 skipped, 0 failed)
  • 5 PlayMode tests pass
  • Focus nudge e2e verified: should_nudge() returns correct results, nudge_unity_focus() successfully focuses Unity via osascript
  • Root logger file handler verified: __name__-based logger entries now appear in log file

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • Removed prefab root rename verification; simplified reconnect timing test; added unit tests for focus-nudge logic and backoff/reset behavior.
  • New Features

    • Background scheduling of focus-nudges to detect and log stalled test runs.
  • Chores

    • Adjusted log routing so module loggers write to the rotating log file.
  • Documentation

    • Updated README to include an additional AI client option.

LoadAssetAtPath returns the asset filename as .name for prefab roots,
not the internally renamed root object name. Remove the rename + assert
that was causing CombinesWithOtherModifications to fail. The test still
verifies that componentProperties works alongside position changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 21, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adjusts a prefab CRUD edit-mode test to stop asserting on a root name change that never occurs, while still validating that componentProperties and position modifications work together.

File-Level Changes

Change Details Files
Update ModifyContents_ComponentProperties_CombinesWithOtherModifications test to no longer expect a prefab root rename.
  • Remove the name field from the modify_contents request payload in the test.
  • Remove the assertion that the reloaded prefab root GameObject name matches the removed name.
  • Keep assertions for position and Rigidbody mass to continue validating combined modifications.
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManagePrefabsCrudTests.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 21, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Removes a prefab-root rename assertion and payload name in a prefab test, drops a frame-yield in a stdio reconnect test, attaches the rotating file handler to the root logger, schedules fire-and-forget Unity focus nudges for stalled running test jobs, and adds unit tests for focus-nudge utilities.

Changes

Cohort / File(s) Summary
Prefab test
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Tools/ManagePrefabsCrudTests.cs
Removed the "name" entry from the modify_contents payload and deleted the assertion that validated prefab-root rename after modification; added a note explaining why root-rename assertions fail.
Stdio reconnect test
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/StdioBridgeReconnectTests.cs
Removed an explicit frame-yield between the second client's handshake and subsequent ping; sends ping immediately and updated the assertion message about stale-client cleanup timing.
Focus-nudge tests
Server/tests/test_focus_nudge.py
Added new unit tests for should_nudge, reset_nudge_backoff, and async nudge_unity_focus covering availability, foreground checks, and backoff/rate-limiting.
Run-tests service
Server/src/services/tools/run_tests.py
Added module-level _background_tasks set; validate no-wait responses are dicts; when a running job appears stalled and should_nudge returns true, schedule a background nudge_unity_focus(...) via asyncio.create_task and track the task for cleanup.
Logging root handler
Server/src/main.py
Also attached the rotating file handler to the root logger so __name__-based loggers write to the same logfile.
Docs
README.md
Updated MCP client list to add "Claude Code" and adjusted the related Claude link.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server_get_test_job as Server.get_test_job
    participant Scheduler as AsyncNudgeScheduler
    participant UnitySvc as nudge_unity_focus

    Client->>Server_get_test_job: Request (no wait_timeout)
    Server_get_test_job->>Server_get_test_job: validate response type/status
    alt response == running and should_nudge == true
        Server_get_test_job->>Scheduler: asyncio.create_task(nudge_unity_focus(project_path,...))
        Scheduler->>UnitySvc: nudge_unity_focus (background)
        UnitySvc-->>Scheduler: (fire-and-forget result)
    end
    Server_get_test_job-->>Client: Immediate MCPResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nudged the server with a gentle hop,
Snipped the rename and skipped a wait-stop,
Logs now cozy in one file's light,
Background nudges hum through the night,
Tiny rabbit smiles — tests leap and hop. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary changes: post-merge fixes covering test corrections, StdioBridge flakiness, and focus nudge functionality improvements.
Description check ✅ Passed The description provides a comprehensive summary with clear sections for changes made and test plan, exceeding the template requirements with detailed context and verification results.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • Consider adding a brief code comment in this test explaining why prefab root names from LoadAssetAtPath<GameObject> can’t be asserted (Unity using the filename as .name), so future maintainers don’t try to reintroduce a similar check.
  • Given that the test no longer verifies a rename, double-check whether the test name and scenario description still accurately reflect its purpose, and rename or clarify if necessary to avoid confusion about what behavior is under test.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider adding a brief code comment in this test explaining why prefab root names from `LoadAssetAtPath<GameObject>` can’t be asserted (Unity using the filename as `.name`), so future maintainers don’t try to reintroduce a similar check.
- Given that the test no longer verifies a rename, double-check whether the test name and scenario description still accurately reflect its purpose, and rename or clarify if necessary to avoid confusion about what behavior is under test.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

dsarno and others added 2 commits February 21, 2026 10:35
The 5-frame yield between client2 handshake and ping created a window
where the MCP Python server could reconnect and close our test client
as stale. Stale-client cleanup runs synchronously in HandleClientAsync
before the read loop, so no yield is needed after reading the handshake.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add file handler to root logger so __name__-based loggers (focus_nudge,
  run_tests) write to the log file instead of only stderr
- Add fire-and-forget nudge check on non-wait_timeout get_test_job calls
  so stalls are detected regardless of client polling style
- Add 13 unit tests for should_nudge logic, backoff reset, and gating

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dsarno dsarno changed the title fix: remove broken root rename assertion from prefab test fix: post-merge fixes for #802 (tests, StdioBridge flakiness, focus nudge) Feb 21, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
Server/tests/test_focus_nudge.py (1)

70-96: LGTM — gating logic is well tested; one optional coverage gap.

The three gates (unavailable, already focused, rate-limited) are correctly exercised. One untested path: _get_frontmost_app() returning None (line 526–528 of focus_nudge.py returns False in that case). Consider adding a fourth case if the gating logic here becomes more complex, but it's not critical.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Server/tests/test_focus_nudge.py` around lines 70 - 96, Add a fourth test to
cover the branch where _get_frontmost_app() returns None: patch
"utils.focus_nudge._is_available" to True and
"utils.focus_nudge._get_frontmost_app" to return None, then call
nudge_unity_focus(force=True or False as appropriate) and assert it returns
False; locate this alongside existing tests for nudge_unity_focus and reference
the functions/variables _get_frontmost_app, _is_available, and nudge_unity_focus
when adding the new test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Server/src/services/tools/run_tests.py`:
- Line 336: The call to
asyncio.create_task(nudge_unity_focus(unity_project_path=project_path)) can be
GC'd because no strong reference is kept; create a module-level sentinel set
(e.g., _background_tasks = set()) near the top of the file and, when scheduling
the task from wherever create_task is called, store the returned Task in that
set, then add a done callback or use add_done_callback to remove the Task from
_background_tasks when it completes; reference the nudge_unity_focus coroutine
and the Task returned by asyncio.create_task to implement this safe
storage-and-cleanup pattern.

---

Nitpick comments:
In `@Server/tests/test_focus_nudge.py`:
- Around line 70-96: Add a fourth test to cover the branch where
_get_frontmost_app() returns None: patch "utils.focus_nudge._is_available" to
True and "utils.focus_nudge._get_frontmost_app" to return None, then call
nudge_unity_focus(force=True or False as appropriate) and assert it returns
False; locate this alongside existing tests for nudge_unity_focus and reference
the functions/variables _get_frontmost_app, _is_available, and nudge_unity_focus
when adding the new test.

dsarno and others added 2 commits February 21, 2026 11:07
- Store asyncio.create_task reference in module-level set to prevent GC (CodeRabbit)
- Add test for _get_frontmost_app() returning None (CodeRabbit nitpick)
- Add comment explaining why prefab root rename is not tested (Sourcery)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dsarno dsarno merged commit 1f6d242 into CoplayDev:beta Feb 21, 2026
1 check passed
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Server/src/services/tools/run_tests.py`:
- Around line 327-328: The bug is that progress = data.get("progress", {})
returns None when the JSON has "progress": null, causing progress.get(...) to
raise; change to use a fallback that also handles explicit None by replacing the
assignment with progress = data.get("progress") or {} so editor_is_focused =
progress.get("editor_is_focused", True) is safe, and apply the identical pattern
to the existing wait_timeout path (replace whatever assigns wait_timeout =
data.get("wait_timeout", ...) with wait_timeout = data.get("wait_timeout") or
<same-default>) in run_tests.py to avoid AttributeError when the key exists but
is null.

Comment on lines +327 to +328
progress = data.get("progress", {})
editor_is_focused = progress.get("editor_is_focused", True)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

progress = data.get("progress", {}) returns None when the key is present with value null.

dict.get(key, default) only substitutes default when the key is absent; an explicit null/None value is returned as-is. Since TestJobProgress | None = None means Unity can legally send "progress": null (e.g., before the first test starts), line 328 will raise AttributeError: 'NoneType' object has no attribute 'get' in that case.

The identical pattern in the pre-existing wait_timeout path (line 285) has the same bug and should be fixed there too.

🐛 Proposed fix (new path at line 327, and matching fix at line 285)
-        progress = data.get("progress", {})
+        progress = data.get("progress") or {}

Apply the same one-line change to the pre-existing wait_timeout path at line 285.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Server/src/services/tools/run_tests.py` around lines 327 - 328, The bug is
that progress = data.get("progress", {}) returns None when the JSON has
"progress": null, causing progress.get(...) to raise; change to use a fallback
that also handles explicit None by replacing the assignment with progress =
data.get("progress") or {} so editor_is_focused =
progress.get("editor_is_focused", True) is safe, and apply the identical pattern
to the existing wait_timeout path (replace whatever assigns wait_timeout =
data.get("wait_timeout", ...) with wait_timeout = data.get("wait_timeout") or
<same-default>) in run_tests.py to avoid AttributeError when the key exists but
is null.

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.

1 participant