Skip to content

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Jan 14, 2026

Summary

  • Fix test job state getting stuck after domain reload
  • Fix refresh_unity failing during domain reload instead of waiting
  • Add automatic focus nudge for stalled PlayMode tests when Unity is unfocused

Changes

  • TestRunnerService.cs: Always clean up job state in RunFinished
  • TestJobManager.cs: Detect and clear stale jobs on startup
  • refresh_unity.py: Add "could not connect" to retryable errors
  • TestRunnerNoThrottle.cs: Add preemptive no-throttle before PlayMode
  • focus_nudge.py: New utility to temporarily focus Unity (cross-platform)
  • run_tests.py: Auto-nudge when tests stall >10s unfocused

Test plan

  • EditMode tests pass
  • PlayMode tests complete reliably with Unity unfocused
  • Focus nudge logs visible in server output
  • Focus returns to original app after nudge

Summary by CodeRabbit

  • New Features

    • Automatic focus nudging to reduce test stalls when the editor loses focus.
    • Preemptive no-throttling for Play Mode test runs to improve reliability.
    • New Play Mode test suite included.
  • Bug Fixes

    • Improved domain-reload resilience with guaranteed payload creation and cleanup.
    • Automatic cleanup of stale/orphaned test jobs after session restore.
    • Broadened error detection and retry logic for connection/domain-reload scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

- TestRunnerService.RunFinished: Always clean up job state even when
  _runCompletionSource is null (happens after PlayMode domain reload)
- TestJobManager: Detect and clear stale jobs (5+ min without updates)
  on startup to recover from stuck state after domain reload
- refresh_unity.py: Add "could not connect" to retryable errors when
  wait_for_ready=True, so connection failures during domain reload
  trigger waiting instead of immediate failure

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

sourcery-ai bot commented Jan 14, 2026

Reviewer's Guide

Adjusts Unity test runner lifecycle handling so test jobs are correctly finalized after domain reloads and stale jobs are auto-recovered on startup, and makes the refresh_unity tool treat connection failures during domain reload as retryable when waiting for Unity to become ready.

Sequence diagram for stale test job recovery on startup

sequenceDiagram
    participant UnityEditorStartup
    participant TestJobManager
    participant TestRunStatus

    UnityEditorStartup->>TestJobManager: TryRestoreFromSessionState()
    TestJobManager->>TestRunStatus: Query isRunning state
    alt TestRunStatus not running but _currentJobId present
        TestJobManager->>TestJobManager: Lookup currentJob in Jobs
        alt currentJob.Status == Running and stale (>5 minutes)
            TestJobManager->>TestJobManager: Mark job Failed
            TestJobManager->>TestJobManager: Set job.Error = "Job orphaned after domain reload"
            TestJobManager->>TestJobManager: job.FinishedUnixMs = now
            TestJobManager->>TestJobManager: _currentJobId = null
        else not stale or not running
            TestJobManager->>TestJobManager: Leave job state unchanged
        end
    else no current job or TestRunStatus still running
        TestJobManager->>TestJobManager: No cleanup needed
    end
Loading

Class diagram for updated test job lifecycle management

classDiagram
    class TestRunnerService {
        - object _runCompletionSource
        - IList leafResults
        + void RunStarted(ITestAdaptor testsToRun)
        + void RunFinished(ITestResultAdaptor result)
    }

    class TestJobManager {
        <<static>>
        - string _currentJobId
        - Dictionary~string, TestJob~ Jobs
        + void OnRunFinished()
        + void FinalizeCurrentJobFromRunFinished(TestRunResult payload)
        + void TryRestoreFromSessionState()
    }

    class TestRunStatus {
        <<static>>
        + void MarkFinished()
        + bool IsRunning()
    }

    class TestJob {
        + string Id
        + TestJobStatus Status
        + string Error
        + long LastUpdateUnixMs
        + long FinishedUnixMs
    }

    class TestRunResult {
        + static TestRunResult Create(ITestResultAdaptor result, IList leafResults)
    }

    class TestJobStatus {
        <<enumeration>>
        Pending
        Running
        Succeeded
        Failed
    }

    TestRunnerService --> TestRunResult : creates
    TestRunnerService --> TestRunStatus : updates
    TestRunnerService --> TestJobManager : notifies
    TestJobManager --> TestJob : manages
    TestJobManager --> TestJobStatus : uses
    TestJob --> TestJobStatus : has
    TestJobManager --> TestRunResult : consumes
    TestRunStatus --> TestJobManager : state queried during restore
Loading

File-Level Changes

Change Details Files
Ensure RunFinished always finalizes test jobs and updates shared state even if the async completion source was lost during domain reload.
  • Remove early return when the run completion source is null in RunFinished.
  • Always build the TestRunResult payload and call TestRunStatus.MarkFinished, TestJobManager.OnRunFinished, and TestJobManager.FinalizeCurrentJobFromRunFinished.
  • Only attempt to complete and clear the _runCompletionSource if it is non-null, so manual runs without an MCP job are handled safely.
MCPForUnity/Editor/Services/TestRunnerService.cs
Detect and clean up stale running test jobs on startup to recover from jobs orphaned by domain reloads.
  • After restoring session state, if _currentJobId is set, look up the job and check if it is still marked Running.
  • Compute a 5-minute staleness threshold using current UTC time in ms and the job's LastUpdateUnixMs.
  • If the job is stale, log a warning, mark it Failed with an orphaned-after-domain-reload error, set FinishedUnixMs, and clear _currentJobId.
MCPForUnity/Editor/Services/TestJobManager.cs
Treat Unity connection failures as retryable when waiting for the editor to become ready in refresh_unity.
  • Normalize error text to lowercase once when extracting the error from the response.
  • Extend retryable conditions to include errors containing 'could not connect' in addition to 'disconnected' or an explicit 'retry' hint.
  • Preserve gating on wait_for_ready and response reason so only appropriate failures are retried.
Server/src/services/tools/refresh_unity.py

Possibly linked issues

  • #(unknown): PR fixes MCP test job state and PlayMode behavior, directly targeting failures when running PlayMode tests via MCP.

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 Jan 14, 2026

Warning

Rate limit exceeded

@dsarno has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 12 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 817bd1e and fa303e2.

📒 Files selected for processing (1)
  • README.md

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds domain-reload resilience in the editor by clearing stale/orphaned test jobs and making RunFinished perform payload creation/cleanup even without a completion source; server-side introduces cross-platform OS focus nudging to unstick stalled test runs and broadens retry logic for Unity connection errors.

Changes

Cohort / File(s) Summary
Editor: job restoration & cleanup
MCPForUnity/Editor/Services/TestJobManager.cs
After restoring session state, detect Running jobs with LastUpdateUnixMs > 5 minutes, mark them Failed with error/finish time, clear current job reference, and log a warning.
Editor: test-run no-throttle & finalization
MCPForUnity/Editor/Services/TestRunnerService.cs, MCPForUnity/Editor/Services/TestRunnerNoThrottle.cs
Add ApplyNoThrottlingPreemptive() and invoke it for PlayMode runs; RunFinished now always builds payload and performs job-finalization/cleanup, but only completes awaiting caller if _runCompletionSource exists.
Server: focus-nudge utilities
Server/src/utils/focus_nudge.py
New cross-platform focus-nudge module with nudge_unity_focus(...), should_nudge(...), platform frontmost/focus helpers, rate-limiting and error handling.
Server: polling integrates nudging
Server/src/services/tools/run_tests.py
Polling loop enhanced to call should_nudge and nudge_unity_focus when jobs appear stalled/unfocused; adds logging/time utilities and nudging-related imports.
Server: wider retry conditions
Server/src/services/tools/refresh_unity.py
Lowercase error extraction and expanded retryable phrases (e.g., includes "disconnected" and "could not connect") to allow retries on domain-reload/connect failures.
Tests & assets: path fixes & PlayMode tests
Server/tests/integration/*, TestProjects/UnityMCPTests/Assets/Tests/PlayMode/*
Integration tests updated to locate server source at src; new PlayMode test assembly, meta files, and PlayModeBasicTests.cs with multiple UnityPlayMode tests added.

Sequence Diagram(s)

sequenceDiagram
    participant Poller as RunTests Poller
    participant DB as Test Job API/State
    participant Unity as Unity Editor
    participant OS as Host OS (focus control)

    Poller->>DB: fetch test job status
    alt job is Running but stalled & editor not focused
        Poller->>Poller: should_nudge(status, editor_is_focused, last_update)
        Poller->>OS: nudge_unity_focus()
        OS->>Unity: bring Unity frontmost
        Unity-->>Poller: may progress job (update)
        OS->>OS: restore previous focus
        Poller->>DB: re-fetch job status
    else job progressing or focused
        Poller->>DB: wait / continue polling
    end
    Poller->>DB: return/finalize job status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • msanatan

Poem

🐰 I hopped through code at break of dawn,
Cleared old jobs the reload left on,
Gently nudged the editor awake,
Let stalled runs stir and progress make,
A tiny rabbit, logging: "All done!" ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: fixing PlayMode tests stalling when unfocused and improving domain reload recovery, which are the core objectives across all modified files.

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


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 found 1 issue, and left some high level feedback:

  • The 5-minute stale job cutoff in TestJobManager.TryRestoreFromSessionState is currently a magic number; consider extracting it to a named constant or config so its intent and tunability are clearer.
  • In the stale job cleanup path, you directly mutate currentJob's status and timestamps; if other code relies on a centralized update/notification method for job state changes, consider routing this through that helper to keep behavior consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The 5-minute stale job cutoff in `TestJobManager.TryRestoreFromSessionState` is currently a magic number; consider extracting it to a named constant or config so its intent and tunability are clearer.
- In the stale job cleanup path, you directly mutate `currentJob`'s status and timestamps; if other code relies on a centralized update/notification method for job state changes, consider routing this through that helper to keep behavior consistent.

## Individual Comments

### Comment 1
<location> `MCPForUnity/Editor/Services/TestJobManager.cs:190` </location>
<code_context>
+                        if (currentJob.Status == TestJobStatus.Running)
+                        {
+                            long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                            long staleCutoffMs = 5 * 60 * 1000; // 5 minutes
+                            if (now - currentJob.LastUpdateUnixMs > staleCutoffMs)
+                            {
</code_context>

<issue_to_address>
**suggestion:** Extract the stale job cutoff into a named constant or configuration to avoid hard-coded timing logic.

Using the inline `5 * 60 * 1000` magic number makes this timeout harder to manage and align with other components. Consider defining a static readonly constant (e.g., `DefaultStaleJobTimeoutMs`) or reading it from config so the stale job policy is centralized and easier to adjust.
</issue_to_address>

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.

if (currentJob.Status == TestJobStatus.Running)
{
long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
long staleCutoffMs = 5 * 60 * 1000; // 5 minutes
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Extract the stale job cutoff into a named constant or configuration to avoid hard-coded timing logic.

Using the inline 5 * 60 * 1000 magic number makes this timeout harder to manage and align with other components. Consider defining a static readonly constant (e.g., DefaultStaleJobTimeoutMs) or reading it from config so the stale job policy is centralized and easier to adjust.

When Unity is unfocused, macOS App Nap (and similar OS features) can
throttle the process, causing PlayMode tests to stall even with Unity
No Throttling mode enabled.

Changes:
- Add ApplyNoThrottlingPreemptive() to TestRunnerNoThrottle for early
  throttle prevention before PlayMode Execute()
- Add focus_nudge.py utility that temporarily focuses Unity and returns
  focus to the original app (supports macOS, Windows, Linux)
- Integrate focus nudge into get_test_job polling - when tests appear
  stalled (unfocused + no progress for 10s), automatically nudge Unity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@dsarno dsarno changed the title Fix test job state management (improved playmode testing) after domain reload; improve refresh tool Fix PlayMode tests stalling when unfocused (python refresh utility), improve domain reload recovery and refresh tool Jan 14, 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

🤖 Fix all issues with AI agents
In `@Server/src/utils/focus_nudge.py`:
- Around line 128-146: The PowerShell script currently interpolates window_title
directly into the script string (see variable window_title and the script block
assigned to script), which can break on special characters or allow injection;
change the script to accept the title as a parameter (e.g., start the script
with "param($title)" and use $title in place of the interpolated
"{window_title}"), and invoke PowerShell with the title passed via -ArgumentList
(or the subprocess equivalent) instead of embedding it; update the call site
that uses _get_frontmost_app_windows() or the literal "Unity" to pass the title
as the argument.
🧹 Nitpick comments (1)
Server/src/utils/focus_nudge.py (1)

315-317: Optional: Redundant import.

The time module is already imported at module level (line 16), so the local import at line 316 is unnecessary.

Suggested fix
     if current_time_ms is None:
-        import time
         current_time_ms = int(time.time() * 1000)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 753ae19 and cb6576f.

📒 Files selected for processing (4)
  • MCPForUnity/Editor/Services/TestRunnerNoThrottle.cs
  • MCPForUnity/Editor/Services/TestRunnerService.cs
  • Server/src/services/tools/run_tests.py
  • Server/src/utils/focus_nudge.py
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-29T15:23:11.613Z
Learnt from: msanatan
Repo: CoplayDev/unity-mcp PR: 491
File: MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs:78-115
Timestamp: 2025-12-29T15:23:11.613Z
Learning: In MCPForUnity, prefer relying on the established testing process to catch UI initialization issues instead of adding defensive null checks for UI elements in editor windows. This means during reviews, verify that tests cover UI initialization paths and that code avoids repetitive null-check boilerplate in Editor Windows. If a UI element can be null, ensure there is a well-tested fallback or that its initialization is guaranteed by design, rather than sprinkling null checks throughout editor code.

Applied to files:

  • MCPForUnity/Editor/Services/TestRunnerNoThrottle.cs
  • MCPForUnity/Editor/Services/TestRunnerService.cs
🧬 Code graph analysis (1)
Server/src/services/tools/run_tests.py (1)
Server/src/utils/focus_nudge.py (2)
  • nudge_unity_focus (227-280)
  • should_nudge (283-320)
🪛 Ruff (0.14.11)
Server/src/utils/focus_nudge.py

43-46: Starting a process with a partial executable path

(S607)


53-53: Do not catch blind exception: Exception

(BLE001)


61-61: subprocess call: check for execution of untrusted input

(S603)


62-62: Starting a process with a partial executable path

(S607)


67-67: Consider moving this statement to an else block

(TRY300)


68-68: Do not catch blind exception: Exception

(BLE001)


93-93: subprocess call: check for execution of untrusted input

(S603)


94-94: Starting a process with a partial executable path

(S607)


101-101: Do not catch blind exception: Exception

(BLE001)


147-147: subprocess call: check for execution of untrusted input

(S603)


148-148: Starting a process with a partial executable path

(S607)


153-153: Consider moving this statement to an else block

(TRY300)


154-154: Do not catch blind exception: Exception

(BLE001)


163-163: Starting a process with a partial executable path

(S607)


170-170: Do not catch blind exception: Exception

(BLE001)


181-181: Starting a process with a partial executable path

(S607)


191-191: subprocess call: check for execution of untrusted input

(S603)


192-192: Starting a process with a partial executable path

(S607)


197-197: Consider moving this statement to an else block

(TRY300)


198-198: Do not catch blind exception: Exception

(BLE001)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Sourcery review
🔇 Additional comments (9)
MCPForUnity/Editor/Services/TestRunnerNoThrottle.cs (1)

76-85: LGTM!

The ApplyNoThrottlingPreemptive() method cleanly addresses the PlayMode transition issue by ensuring no-throttling is applied before Execute() is called. The implementation correctly marks the test run as active first (for domain reload recovery), then applies the settings. The documentation clearly explains the purpose.

MCPForUnity/Editor/Services/TestRunnerService.cs (2)

113-120: LGTM!

Good defensive approach for PlayMode tests. The preemptive no-throttling application ensures Unity isn't throttled during the multi-frame Play mode transition, which can otherwise prevent RunStarted from ever being called when Unity is backgrounded.


194-213: LGTM - Robust domain reload handling.

The restructured RunFinished correctly ensures:

  1. Payload is always created regardless of completion source state
  2. All cleanup methods execute unconditionally (safe for manual test runs too)
  3. Result delivery only attempts if there's an awaiting caller

This properly handles the domain reload scenario where _runCompletionSource is lost but TestJobManager state persists via SessionState.

Server/src/services/tools/run_tests.py (2)

5-6: LGTM!

Clean imports for the new focus nudging functionality. The module-level logger follows the standard pattern.

Also applies to: 19-21


219-235: LGTM - Focus nudge integration.

The focus nudge logic is well-integrated:

  • Correctly extracts editor_is_focused from progress with a safe default of True (no nudge if unknown)
  • Uses the should_nudge helper to encapsulate stall detection logic
  • 10-second stall threshold is reasonable
  • Logging provides visibility into nudge events

The nudge is appropriately placed before the sleep, giving Unity time to process after being focused.

Server/src/utils/focus_nudge.py (4)

1-36: LGTM - Platform availability checks.

Good defensive setup with platform-specific tool detection. The availability check ensures graceful degradation when required tools aren't present.


227-280: LGTM - Well-designed nudge function.

Good implementation with:

  • Rate limiting using monotonic time (correct for elapsed time measurement)
  • Early exits for unavailable platforms and already-focused Unity
  • Graceful handling of failures at each step
  • Focus restoration to original app

283-320: LGTM - Clean decision logic.

The should_nudge function encapsulates the stall detection logic well:

  • Only nudges running jobs
  • Respects Unity's self-reported focus state
  • Handles the "no updates yet" edge case appropriately
  • Configurable stall threshold

39-71: Regarding static analysis hints - acceptable patterns.

The Ruff warnings for this file are largely acceptable given the context:

  • S607 (partial executable path): osascript, powershell, and xdotool are standard system utilities expected to be on PATH
  • BLE001 (blind exception): Catching broad exceptions is intentional here for graceful degradation - the utility should never crash the caller
  • TRY300 (return in try): Minor style preference, no functional impact

These patterns are appropriate for a best-effort utility that must handle diverse system configurations gracefully.

Also applies to: 159-200

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 128 to 146
else:
# Try to find window by title
script = f'''
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Win32 {{
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}}
"@
$proc = Get-Process | Where-Object {{$_.MainWindowTitle -eq "{window_title}"}} | Select-Object -First 1
if ($proc) {{
[Win32]::ShowWindow($proc.MainWindowHandle, 9)
[Win32]::SetForegroundWindow($proc.MainWindowHandle)
}}
'''
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

Minor: Unsanitized string interpolation in PowerShell script.

The window_title parameter is interpolated directly into the PowerShell script at line 141. While the current usage only passes titles retrieved from _get_frontmost_app_windows() or the literal "Unity", this pattern is fragile. If a window title contains special characters (e.g., quotes, backticks, $), it could break the script or potentially allow command injection.

Consider escaping the window title or using a safer parameter passing mechanism:

Suggested fix using escaped string
         else:
             # Try to find window by title
+            # Escape special PowerShell characters in window title
+            escaped_title = window_title.replace('"', '`"').replace('$', '`$')
             script = f'''
 Add-Type @"
 using System;
 using System.Runtime.InteropServices;
 public class Win32 {{
     [DllImport("user32.dll")]
     public static extern bool SetForegroundWindow(IntPtr hWnd);
     [DllImport("user32.dll")]
     public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
 }}
 "@
-$proc = Get-Process | Where-Object {{$_.MainWindowTitle -eq "{window_title}"}} | Select-Object -First 1
+$proc = Get-Process | Where-Object {{$_.MainWindowTitle -eq "{escaped_title}"}} | Select-Object -First 1
 if ($proc) {{
     [Win32]::ShowWindow($proc.MainWindowHandle, 9)
     [Win32]::SetForegroundWindow($proc.MainWindowHandle)
 }}
 '''
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else:
# Try to find window by title
script = f'''
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Win32 {{
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}}
"@
$proc = Get-Process | Where-Object {{$_.MainWindowTitle -eq "{window_title}"}} | Select-Object -First 1
if ($proc) {{
[Win32]::ShowWindow($proc.MainWindowHandle, 9)
[Win32]::SetForegroundWindow($proc.MainWindowHandle)
}}
'''
else:
# Try to find window by title
# Escape special PowerShell characters in window title
escaped_title = window_title.replace('"', '`"').replace('$', '`$')
script = f'''
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Win32 {{
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}}
"@
$proc = Get-Process | Where-Object {{$_.MainWindowTitle -eq "{escaped_title}"}} | Select-Object -First 1
if ($proc) {{
[Win32]::ShowWindow($proc.MainWindowHandle, 9)
[Win32]::SetForegroundWindow($proc.MainWindowHandle)
}}
'''
🤖 Prompt for AI Agents
In `@Server/src/utils/focus_nudge.py` around lines 128 - 146, The PowerShell
script currently interpolates window_title directly into the script string (see
variable window_title and the script block assigned to script), which can break
on special characters or allow injection; change the script to accept the title
as a parameter (e.g., start the script with "param($title)" and use $title in
place of the interpolated "{window_title}"), and invoke PowerShell with the
title passed via -ArgumentList (or the subprocess equivalent) instead of
embedding it; update the call site that uses _get_frontmost_app_windows() or the
literal "Unity" to pass the title as the argument.

dsarno and others added 3 commits January 14, 2026 14:12
- Remove redundant time import (already imported at module level)
- Escape window titles in PowerShell script to prevent injection
- Remove unused Callable import

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Improve logging in focus_nudge.py: rate limit skip and focus return at INFO level
- Improve logging in run_tests.py: show nudge completion status
- Fix path resolution in test_logging_stdout.py and test_transport_framing.py
- Add PlayMode tests to UnityMCPTests project for testing PlayMode runner

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When running PlayMode tests with Unity in the background, the focus
nudge feature may trigger OS permission prompts (especially on macOS
for accessibility/automation). Document this expected behavior.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@dsarno dsarno merged commit 39bff4a into CoplayDev:main Jan 14, 2026
2 checks passed
@dsarno dsarno deleted the fix/test-job-domain-reload branch January 15, 2026 16:04
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