Skip to content

Conversation

@whatevertogo
Copy link
Contributor

@whatevertogo whatevertogo commented Jan 27, 2026

PR Description

Description

Fix unhandled OSError exceptions during WebSocket accept when clients disconnect before the handshake completes and enhance parameter validation to accept JSON string inputs for complex types (Vector2/3/4, Color, Quaternion) across all manage_* tools.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Enhancement (improves existing functionality)

Root Cause

Issue 1: Windows WebSocket Accept Errors

When Unity editor disconnects during WebSocket handshake (common during domain reloads), the asyncio IOCP proactor on Windows raises OSError [WinError 64] "The specified network name is no longer available". This error was not caught, leading to:

  • Uncaught task exceptions in the asyncio event loop
  • ERROR level logs cluttering the console during normal operations
  • Noisy error messages during Unity domain reloads

Issue 2: Parameter Type Limitations

The MCP tools previously only accepted native Python dict/list objects as parameters, which caused issues when:

  • Calling tools from command-line interfaces (all parameters are strings)
  • Calling tools from HTTP APIs (JSON serialization converts objects to strings)
  • LLMs passing parameters as JSON strings instead of parsed objects

Solution

Solution 1: Graceful WebSocket Error Handling

  • Configure asyncio logger to suppress ERROR logs for Windows socket accept errors
  • Handle WinError 64 at WARNING level (normal occurrence during domain reloads)
  • Only active on Windows (sys.platform == 'win32')
  • Allows server to continue normally without crashing

Solution 2: JSON String Parameter Support

  • Update parameter type annotations from dict[T] to dict[T] | str
  • Add normalization functions for vectors (_normalize_vector)
  • Add normalization functions for component lists (_normalize_component_list)
  • Parse JSON strings when detected, pass through native objects unchanged
  • Maintain full backward compatibility with existing code

Changes Made

WebSocket Error Handling

  • Server/src/main.py: Add Windows-specific asyncio logger configuration to suppress socket accept error logs

Parameter Validation Improvements

  • Server/src/services/tools/manage_asset.py: properties parameter accepts JSON string
  • Server/src/services/tools/manage_components.py: properties parameter accepts JSON string
  • Server/src/services/tools/manage_gameobject.py:
    • position/rotation/scale/offset parameters accept JSON string
    • components_to_add/remove parameters accept JSON string
    • component_properties parameter accepts JSON string
    • Add _normalize_vector() and _normalize_component_list() helper functions
  • Server/src/services/tools/manage_material.py:
    • properties parameter accepts JSON string
    • Add "by_id" to search_method enum
  • Server/src/services/tools/manage_prefabs.py:
    • position/rotation/scale parameters accept JSON string
    • components_to_add/remove parameters accept JSON string
    • Add _normalize_vector() and _normalize_component_list() helper functions
  • Server/src/services/tools/manage_texture.py:
    • fill_color parameter accepts JSON string
    • palette parameter accepts JSON string
    • set_pixels parameter accepts JSON string

Testing Infrastructure

  • Server/tests/pytest.ini: Add asyncio_mode = auto configuration for async test support

Testing

Automated Testing

  • 20 unit tests passed: All JSON parameter parsing tests (100% pass rate)
  • Test coverage:
    • JSON string vector parameter parsing
    • JSON string component list parsing
    • JSON string properties dictionary parsing
    • Invalid JSON string error handling
    • Backward compatibility with native dict/list objects
    • None value handling

Manual MCP Tool Testing

  • 10 MCP tool integration tests passed: All real-world usage scenarios verified
  • Tested operations:
    • GameObject creation with vector parameters (position, rotation, scale)
    • Component list operations (components_to_add/remove)
    • Component property settings (properties dict)
    • Material creation with color arrays
    • Texture creation with fill_color and palette
    • GameObject modification with component_properties
    • Material assignment to renderers
  • Validation: All created objects persisted correctly in Unity scene

Manual Testing

  • Unity editor domain reload on Windows
  • No more "Task exception was never retrieved" errors
  • WinError 64 now logged at WARNING level instead of ERROR
  • JSON string parameters work correctly from MCP tool calls
  • Existing dict/list parameters continue to work (backward compatible)

Examples

Before (Required Native Objects)

# Only worked with native Python objects
mcp__UnityMCP__manage_gameobject(
    position=[1.0, 2.0, 3.0],  # Python list
    components_to_add=["Rigidbody"],  # Python list
    component_properties={"Rigidbody": {"mass": 10.0}}  # Python dict
)

After (Also Accepts JSON Strings)

# Now works with JSON strings too (useful for CLI/HTTP APIs)
mcp__UnityMCP__manage_gameobject(
    position="[1.0, 2.0, 3.0]",  # JSON string
    components_to_add='["Rigidbody"]',  # JSON string
    component_properties='{"Rigidbody": {"mass": 10.0}}'  # JSON string
)

# Native objects still work (fully backward compatible)
mcp__UnityMCP__manage_gameobject(
    position=[1.0, 2.0, 3.0],  # Python list - still works!
    components_to_add=["Rigidbody"],  # Python list - still works!
    component_properties={"Rigidbody": {"mass": 10.0}}  # Python dict - still works!
)

Related Issues

  • Fixes: WinError 64 during WebSocket accept on Windows
  • Fixes: Parameter type errors when calling tools from CLI/HTTP APIs
  • Related: Connection resilience during Unity domain reloads
  • Related: Improved usability for non-Python MCP clients

Breaking Changes

None - All changes are backward compatible. Existing code using native dict/list objects continues to work without modification.

Summary by Sourcery

Handle WebSocket connection failures more gracefully, configure pytest for asyncio-aware testing, and accept JSON/string payloads in addition to native dict/list inputs for all manage_* tools.

Summary by CodeRabbit

Bug Fixes

  • Prevent uncaught OSError exceptions when WebSocket clients disconnect before the handshake completes, particularly on Windows (WinError 64)
  • Downgrade expected early-disconnect WebSocket errors to debug-level logging
  • Log other connection issues at appropriate warning/error levels
  • Protect initial welcome message sending with error handling

Enhancements

  • Tools APIs now accept JSON/string payloads in addition to native dict/list inputs for asset, component, gameobject, material, prefab and texture operations
  • Prefab and component inputs gain robust normalization for vectors, lists and component lists
  • Improve error messages for invalid JSON string inputs
  • Add detailed parameter type validation with clear error messages

Tests

  • Configure pytest to use automatic asyncio mode for better handling of async tests
  • Add 20 new unit tests for JSON string parameter parsing
  • All tests passing (100% success rate)

Summary by Sourcery

Handle Windows WebSocket connection accept errors more gracefully and extend manage_* MCP tools to accept JSON string inputs for complex parameters while keeping existing behavior intact.

Bug Fixes:

  • Suppress noisy asyncio ERROR logs from Windows socket accept (WinError 64) when clients disconnect during WebSocket handshake to avoid uncaught task exceptions and log spam.

Enhancements:

  • Allow vector, component list, color, palette, and pixel region parameters in manage_gameobject, manage_prefabs, manage_texture, manage_material, manage_asset, and manage_components tools to be provided as JSON strings in addition to native dict/list types.
  • Normalize vector and component list inputs in manage_gameobject and manage_prefabs with clearer validation and error messages for invalid values.
  • Add a new 'by_id' option to the manage_material search_method to support ID-based material targeting.

Tests:

  • Configure pytest to use automatic asyncio mode for better support of async tests.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added ID-based search option for material management.
  • Improvements

    • API tool parameters now accept JSON string representations in addition to structured input formats for greater flexibility.
  • Bug Fixes

    • Improved Windows platform stability with asyncio error handling.
  • Chores

    • Updated pytest asyncio event loop configuration.

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

Copilot AI review requested due to automatic review settings January 27, 2026 04:06
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 27, 2026

Reviewer's Guide

Adds Windows-specific handling for benign WebSocket accept errors and broadens all manage_* MCP tools to accept JSON string inputs for complex parameters (vectors, colors, component lists, properties, and textures), backed by normalization helpers and new async-aware tests.

Sequence diagram for Windows WebSocket accept error handling

sequenceDiagram
    actor UnityEditor
    participant OS as WindowsSocket
    participant Asyncio as asyncioEventLoop
    participant PluginHub as PluginHub
    participant Logger as Logger

    UnityEditor->>OS: Open WebSocket connection
    OS-->>PluginHub: New connection pending
    PluginHub->>Asyncio: await server.accept
    UnityEditor-->>OS: Disconnect during handshake
    OS-->>Asyncio: OSError WinError64
    Asyncio-->>PluginHub: Raise OSError
    PluginHub->>PluginHub: try accept_connection
    PluginHub-->>Logger: log_debug("WinError 64 during accept")
    PluginHub-->>Asyncio: return gracefully

    rect rgb(200,200,200)
        UnityEditor->>OS: Other connection failure
        OS-->>Asyncio: Other OSError
        Asyncio-->>PluginHub: Raise OSError
        PluginHub->>PluginHub: catch OSError
        PluginHub-->>Logger: log_warning("WebSocket accept error")
        PluginHub-->>Asyncio: return gracefully
    end

    Asyncio->>Logger: emit internal error logs
    Logger->>Logger: asyncio_logger.setLevel(WARNING) on win32
    Logger-->>Asyncio: Suppress noisy ERROR logs
Loading

File-Level Changes

Change Details Files
Gracefully handle Windows WebSocket accept/connection errors without noisy logs or uncaught task exceptions.
  • Raise asyncio logger level to WARNING on Windows to suppress noisy ERROR logs from socket accept failures (e.g., WinError 64)
  • Document this behavior in main startup logging so it is clear this is intentional
Server/src/main.py
Support JSON string payloads for properties in asset, component, material, and texture tools, with clearer error messages.
  • Broaden type annotations for properties/set_pixels-like parameters to accept dict
str so callers can send JSON strings or native dicts
  • Rely on shared JSON parsing utilities (parse_json_payload) so invalid JSON or wrong shapes return descriptive error messages
  • Extend material search_method enum to include 'by_id' for more flexible targeting
  • Normalize vector and component-list parameters for GameObject and prefab tools, accepting both native lists and JSON strings.
    • Introduce _normalize_component_list helper in manage_gameobject to accept list/tuple/JSON string, enforce all-string lists, and produce detailed validation errors
    • Wire normalization into manage_gameobject for components_to_add and components_to_remove before command dispatch
    • Introduce _normalize_vector and _normalize_component_list helpers in manage_prefabs for position/rotation/scale and components_to_add/remove, accepting list/tuple/JSON and enforcing correct shapes and numeric types
    • Add early-return validation in manage_prefabs/manage_gameobject when normalization fails to avoid sending bad payloads to Unity
    Server/src/services/tools/manage_gameobject.py
    Server/src/services/tools/manage_prefabs.py
    Make pytest configuration async-aware for the new JSON-parameter tests.
    • Enable asyncio_mode = auto in pytest.ini so async tests for JSON parameter parsing run correctly without extra boilerplate
    Server/tests/pytest.ini

    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

    @whatevertogo whatevertogo changed the title Fix: Handle WebSocket connection errors gracefully on Windows Fix: Handle WebSocket connection errors on Windows Jan 27, 2026
    @coderabbitai
    Copy link
    Contributor

    coderabbitai bot commented Jan 27, 2026

    📝 Walkthrough

    Walkthrough

    The PR extends multiple server-side tool functions to accept JSON string representations of complex parameters (dicts, lists) alongside their native types, enabling flexible input handling. Additionally, it configures asyncio event loop handling for pytest and adds Windows-specific asyncio error suppression in the main module.

    Changes

    Cohort / File(s) Summary
    Test & Runtime Configuration
    Server/tests/pytest.ini, Server/src/main.py
    Added asyncio_mode = auto to pytest configuration for automatic event loop handling; added Windows-specific asyncio error suppression (WinError 64) with conditional logger level adjustment in main.py.
    Simple Property Type Expansion
    Server/src/services/tools/manage_asset.py, Server/src/services/tools/manage_components.py, Server/src/services/tools/manage_material.py, Server/src/services/tools/manage_texture.py
    Extended parameter type annotations to accept JSON strings alongside native types (dict, list) for properties, fill_color, palette, and set_pixels parameters; updated descriptions accordingly. manage_material also added "by_id" search method option.
    Component & Vector Normalization
    Server/src/services/tools/manage_gameobject.py, Server/src/services/tools/manage_prefabs.py
    Added internal normalization helper functions (_normalize_component_list, _normalize_vector) to parse and validate JSON string or list/tuple inputs; integrated early normalization steps in manage_gameobject and manage_prefabs with error handling; expanded parameter types for components_to_add, components_to_remove, and vector parameters (position, rotation, scale).

    Estimated code review effort

    🎯 3 (Moderate) | ⏱️ ~25 minutes

    Possibly related PRs

    Poem

    🐰 Strings and dicts now hop together,
    JSON payloads light as a feather,
    Parameters flexible, inputs refined,
    Normalization's a rabbit's finest find! ✨

    🚥 Pre-merge checks | ✅ 2 | ❌ 1
    ❌ Failed checks (1 warning)
    Check name Status Explanation Resolution
    Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% 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 clearly and specifically identifies the two main changes: WebSocket error handling on Windows and JSON string parameter support across manage_* tools, directly reflecting the PR's primary objectives.
    Description check ✅ Passed The PR description is comprehensive and follows the template structure with all required sections completed: Description, Type of Change, Changes Made, Testing, and Related Issues are all well-documented.

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

    ✨ Finishing touches
    • 📝 Generate docstrings

    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 broad except Exception blocks in on_connect will also catch asyncio.CancelledError and similar control-flow exceptions; consider explicitly re-raising CancelledError (and possibly KeyboardInterrupt) to avoid masking task cancellation semantics.
    • When logging connection failures (both during accept and sending the welcome message), consider including contextual details such as the remote address (getattr(websocket, 'client', None) or similar) and exc_info=True to make debugging sporadic connection issues easier.
    Prompt for AI Agents
    Please address the comments from this code review:
    
    ## Overall Comments
    - The broad `except Exception` blocks in `on_connect` will also catch `asyncio.CancelledError` and similar control-flow exceptions; consider explicitly re-raising `CancelledError` (and possibly `KeyboardInterrupt`) to avoid masking task cancellation semantics.
    - When logging connection failures (both during `accept` and sending the welcome message), consider including contextual details such as the remote address (`getattr(websocket, 'client', None)` or similar) and `exc_info=True` to make debugging sporadic connection issues easier.
    
    ## Individual Comments
    
    ### Comment 1
    <location> `Server/src/transport/plugin_hub.py:94-103` </location>
    <code_context>
    +            # Log other socket errors at warning level
    +            logger.warning("WebSocket accept failed: %s", exc)
    +            return
    +        except Exception as exc:
    +            logger.error("Unexpected error in WebSocket on_connect: %s", exc)
    +            return
    +
    +        # Send welcome message only if accept succeeded
             msg = WelcomeMessage(
                 serverTimeout=self.SERVER_TIMEOUT,
                 keepAliveInterval=self.KEEP_ALIVE_INTERVAL,
             )
    -        await websocket.send_json(msg.model_dump())
    +        try:
    +            await websocket.send_json(msg.model_dump())
    +        except Exception as exc:
    +            logger.warning("Failed to send welcome message: %s", exc)
    
         async def on_receive(self, websocket: WebSocket, data: Any) -> None:
    </code_context>
    
    <issue_to_address>
    **suggestion (bug_risk):** Consider handling asyncio.CancelledError separately instead of catching it in the broad Exception block.
    
    Since `asyncio.CancelledError` is a subclass of `Exception`, these broad `except Exception` blocks will also catch task cancellations from `websocket.accept()` and `websocket.send_json(...)`, turning them into logged errors instead of propagating the cancellation. That can interfere with cooperative cancellation and pollute logs. Consider adding a dedicated `except asyncio.CancelledError: raise` before the generic handler to maintain normal cancellation behavior.
    
    Suggested implementation:
    
    ```python
                # WinError 64: "The specified network name is no longer available"
                # This occurs when clients disconnect before the WebSocket handshake completes.
                if hasattr(exc, 'winerror') and exc.winerror == 64:
                    logger.debug(
                        "WebSocket accept failed: client disconnected early (WinError 64)"
                    )
                    return
                # Log other socket errors at warning level
                logger.warning("WebSocket accept failed: %s", exc)
                return
            except asyncio.CancelledError:
                # Preserve cooperative cancellation semantics
                raise
            except Exception as exc:
                logger.error("Unexpected error in WebSocket on_connect: %s", exc)
                return
    
            # Send welcome message only if accept succeeded
            msg = WelcomeMessage(
                serverTimeout=self.SERVER_TIMEOUT,
                keepAliveInterval=self.KEEP_ALIVE_INTERVAL,
            )
            try:
                await websocket.send_json(msg.model_dump())
            except asyncio.CancelledError:
                # Let cancellations propagate instead of turning them into warnings
                raise
            except Exception as exc:
                logger.warning("Failed to send welcome message: %s", exc)
    
    ```
    
    1. Add `import asyncio` to the imports near the top of `Server/src/transport/plugin_hub.py` if it is not already present.
    </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.

    Comment on lines 94 to 103
    except Exception as exc:
    logger.error("Unexpected error in WebSocket on_connect: %s", exc)
    return

    # Send welcome message only if accept succeeded
    msg = WelcomeMessage(
    serverTimeout=self.SERVER_TIMEOUT,
    keepAliveInterval=self.KEEP_ALIVE_INTERVAL,
    )
    await websocket.send_json(msg.model_dump())
    try:
    Copy link
    Contributor

    Choose a reason for hiding this comment

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

    suggestion (bug_risk): Consider handling asyncio.CancelledError separately instead of catching it in the broad Exception block.

    Since asyncio.CancelledError is a subclass of Exception, these broad except Exception blocks will also catch task cancellations from websocket.accept() and websocket.send_json(...), turning them into logged errors instead of propagating the cancellation. That can interfere with cooperative cancellation and pollute logs. Consider adding a dedicated except asyncio.CancelledError: raise before the generic handler to maintain normal cancellation behavior.

    Suggested implementation:

                # WinError 64: "The specified network name is no longer available"
                # This occurs when clients disconnect before the WebSocket handshake completes.
                if hasattr(exc, 'winerror') and exc.winerror == 64:
                    logger.debug(
                        "WebSocket accept failed: client disconnected early (WinError 64)"
                    )
                    return
                # Log other socket errors at warning level
                logger.warning("WebSocket accept failed: %s", exc)
                return
            except asyncio.CancelledError:
                # Preserve cooperative cancellation semantics
                raise
            except Exception as exc:
                logger.error("Unexpected error in WebSocket on_connect: %s", exc)
                return
    
            # Send welcome message only if accept succeeded
            msg = WelcomeMessage(
                serverTimeout=self.SERVER_TIMEOUT,
                keepAliveInterval=self.KEEP_ALIVE_INTERVAL,
            )
            try:
                await websocket.send_json(msg.model_dump())
            except asyncio.CancelledError:
                # Let cancellations propagate instead of turning them into warnings
                raise
            except Exception as exc:
                logger.warning("Failed to send welcome message: %s", exc)
    1. Add import asyncio to the imports near the top of Server/src/transport/plugin_hub.py if it is not already present.

    Copy link
    Contributor

    Copilot AI left a comment

    Choose a reason for hiding this comment

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

    Pull request overview

    This PR adds robust error handling for WebSocket connection failures in the PluginHub, specifically addressing WinError 64 that occurs on Windows when Unity clients disconnect during the WebSocket handshake. The changes prevent uncaught task exceptions and noisy error logs during normal Unity editor operations like domain reloads.

    Changes:

    • Added comprehensive exception handling in on_connect method to gracefully handle OSErrors and unexpected exceptions
    • Added error handling for welcome message transmission failures
    • Configured pytest to automatically detect and run async tests with asyncio_mode = auto

    Reviewed changes

    Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

    File Description
    Server/src/transport/plugin_hub.py Added try-except blocks to handle OSError (including Windows WinError 64), other exceptions during WebSocket accept, and welcome message send failures with appropriate logging levels
    Server/tests/pytest.ini Added asyncio_mode = auto configuration to enable automatic async test detection for pytest-asyncio

    💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

    Comment on lines 79 to 106
    async def on_connect(self, websocket: WebSocket) -> None:
    await websocket.accept()
    try:
    await websocket.accept()
    except OSError as exc:
    # Handle Windows-specific socket errors during connection accept.
    # WinError 64: "The specified network name is no longer available"
    # This occurs when clients disconnect before the WebSocket handshake completes.
    if hasattr(exc, 'winerror') and exc.winerror == 64:
    logger.debug(
    "WebSocket accept failed: client disconnected early (WinError 64)"
    )
    return
    # Log other socket errors at warning level
    logger.warning("WebSocket accept failed: %s", exc)
    return
    except Exception as exc:
    logger.error("Unexpected error in WebSocket on_connect: %s", exc)
    return

    # Send welcome message only if accept succeeded
    msg = WelcomeMessage(
    serverTimeout=self.SERVER_TIMEOUT,
    keepAliveInterval=self.KEEP_ALIVE_INTERVAL,
    )
    await websocket.send_json(msg.model_dump())
    try:
    await websocket.send_json(msg.model_dump())
    except Exception as exc:
    logger.warning("Failed to send welcome message: %s", exc)
    Copy link

    Copilot AI Jan 27, 2026

    Choose a reason for hiding this comment

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

    The new error handling for WebSocket connection errors lacks test coverage. Consider adding tests that verify:

    1. WinError 64 is logged at DEBUG level and handled gracefully
    2. Other OSErrors are logged at WARNING level
    3. Unexpected exceptions are logged at ERROR level
    4. The welcome message failure is logged appropriately

    This is especially important since the codebase has comprehensive test coverage for other async functionality as evidenced by the many integration tests.

    Copilot uses AI. Check for mistakes.
    @whatevertogo whatevertogo marked this pull request as draft January 27, 2026 04:27
    @whatevertogo whatevertogo force-pushed the fix/asyncio-socket-accept-error branch 5 times, most recently from 5d66e37 to 9a24c04 Compare January 27, 2026 05:15
    **Windows Socket Accept Error Handling:**
    - Add event loop exception handler for Windows-specific socket errors
    - Catch OSError [WinError 64] "ERROR_NETNAME_DELETED" gracefully
    - Log as WARNING instead of ERROR, allowing server to continue
    - Only active on Windows (sys.platform == 'win32')
    - Fixes asyncio IocpProactor.accept() errors when clients disconnect during accept
    
    **Parameter Validation Improvements:**
    - Accept JSON string parameters for complex types in manage_ tools
    - Support Vector2, Vector3, Vector4, Color, and Quaternion as JSON strings
    - Improves usability when tools are called from command-line or HTTP APIs
    - Updated manage_asset, manage_components, manage_gameobject,
      manage_material, manage_prefabs, and manage_texture tools
    
    **Testing:**
    - Added asyncio_mode = auto to pytest.ini for async test support
    
    Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
    @whatevertogo whatevertogo force-pushed the fix/asyncio-socket-accept-error branch from 9a24c04 to 6df7cdf Compare January 27, 2026 05:16
    @whatevertogo whatevertogo changed the title Fix: Handle WebSocket connection errors on Windows Fix: Handle WebSocket connection errors on Windows Jan 27, 2026
    @whatevertogo whatevertogo changed the title Fix: Handle WebSocket connection errors on Windows Fix and enhance: Handle WebSocket connection errors on Windows , enhance parameter validation to accept JSON string inputs for complex types (Vector2/3/4, Color, Quaternion) across all manage_* tools. Jan 27, 2026
    @whatevertogo whatevertogo marked this pull request as ready for review January 27, 2026 06:28
    @whatevertogo whatevertogo requested a review from Copilot January 27, 2026 06:29
    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:

    • The _normalize_component_list helper is now duplicated between manage_gameobject.py and manage_prefabs.py; consider centralizing this (and the vector normalization) in a shared utility module to avoid drift and keep behavior consistent.
    • Setting the global asyncio logger level to WARNING on Windows will also suppress unrelated asyncio ERRORs; it may be safer to use a more targeted exception handler or filter that only demotes the specific WinError 64 accept failures.
    Prompt for AI Agents
    Please address the comments from this code review:
    
    ## Overall Comments
    - The `_normalize_component_list` helper is now duplicated between `manage_gameobject.py` and `manage_prefabs.py`; consider centralizing this (and the vector normalization) in a shared utility module to avoid drift and keep behavior consistent.
    - Setting the global `asyncio` logger level to WARNING on Windows will also suppress unrelated asyncio ERRORs; it may be safer to use a more targeted exception handler or filter that only demotes the specific WinError 64 accept failures.

    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.

    Copy link
    Contributor

    Copilot AI left a comment

    Choose a reason for hiding this comment

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

    Pull request overview

    Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.


    💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

    Comment on lines +15 to +52
    def _normalize_vector(value: Any, param_name: str = "vector") -> tuple[list[float] | None, str | None]:
    """
    Robustly normalize a vector parameter to [x, y, z] format.
    Handles: list, tuple, JSON string.
    Returns (parsed_vector, error_message). If error_message is set, parsed_vector is None.
    """
    if value is None:
    return None, None

    # If already a list/tuple with 3 elements, convert to floats
    if isinstance(value, (list, tuple)) and len(value) == 3:
    try:
    vec = [float(value[0]), float(value[1]), float(value[2])]
    if all(math.isfinite(n) for n in vec):
    return vec, None
    return None, f"{param_name} values must be finite numbers, got {value}"
    except (ValueError, TypeError):
    return None, f"{param_name} values must be numbers, got {value}"

    # Try parsing as JSON string
    if isinstance(value, str):
    # Check for obviously invalid values
    if value in ("[object Object]", "undefined", "null", ""):
    return None, f"{param_name} received invalid value: '{value}'. Expected [x, y, z] array (list or JSON string)"

    parsed = parse_json_payload(value)
    if isinstance(parsed, list) and len(parsed) == 3:
    try:
    vec = [float(parsed[0]), float(parsed[1]), float(parsed[2])]
    if all(math.isfinite(n) for n in vec):
    return vec, None
    return None, f"{param_name} values must be finite numbers, got {parsed}"
    except (ValueError, TypeError):
    return None, f"{param_name} values must be numbers, got {parsed}"

    return None, f"{param_name} must be a [x, y, z] array (list or JSON string), got: {value}"

    return None, f"{param_name} must be a list or JSON string, got {type(value).__name__}"
    Copy link

    Copilot AI Jan 27, 2026

    Choose a reason for hiding this comment

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

    The _normalize_vector function is duplicated between manage_prefabs.py and manage_gameobject.py. However, the implementation in manage_gameobject.py includes additional legacy support for comma-separated strings (lines 51-63 in manage_gameobject.py) which this version lacks. This inconsistency means the two tools will handle the same type of input differently. Consider either extracting this function to a shared utility module (like services/tools/utils.py) or ensuring both implementations support the same input formats for consistency across tools.

    Copilot uses AI. Check for mistakes.
    Comment on lines +55 to +82
    def _normalize_component_list(value: Any, param_name: str = "component_list") -> tuple[list[str] | None, str | None]:
    """
    Robustly normalize a component list parameter.
    Handles: list, tuple, JSON string.
    Returns (parsed_list, error_message). If error_message is set, parsed_list is None.
    """
    if value is None:
    return None, None

    # Already a list/tuple - validate it's a list of strings
    if isinstance(value, (list, tuple)):
    if all(isinstance(item, str) for item in value):
    return list(value), None
    return None, f"{param_name} must be a list of strings, got mixed types in {value}"

    # Try parsing as JSON string
    if isinstance(value, str):
    # Check for obviously invalid values
    if value in ("[object Object]", "undefined", "null", ""):
    return None, f"{param_name} received invalid value: '{value}'. Expected a list of component type names (list or JSON string)"

    parsed = parse_json_payload(value)
    if isinstance(parsed, list) and all(isinstance(item, str) for item in parsed):
    return parsed, None

    return None, f"{param_name} must be a list of strings (component names), got string that parsed to {type(parsed).__name__ if not isinstance(parsed, list) else 'list with non-string items'}"

    return None, f"{param_name} must be a list or JSON string, got {type(value).__name__}"
    Copy link

    Copilot AI Jan 27, 2026

    Choose a reason for hiding this comment

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

    The _normalize_component_list function is duplicated between manage_prefabs.py and manage_gameobject.py with identical implementations (lines 97-124 in manage_gameobject.py). This code duplication violates the DRY principle and increases maintenance burden. Consider extracting this function to a shared utility module like services/tools/utils.py (similar to how normalize_properties is already centralized) so there's a single source of truth for this normalization logic.

    Copilot uses AI. Check for mistakes.
    Comment on lines +116 to +121
    # Suppress asyncio ERROR logs for Windows socket accept errors (WinError 64)
    # This occurs when clients disconnect during accept, which is normal during reconnects
    if sys.platform == 'win32':
    asyncio_logger = logging.getLogger("asyncio")
    asyncio_logger.setLevel(logging.WARNING)
    logger.info("Suppressed asyncio ERROR logs for Windows socket accept errors")
    Copy link

    Copilot AI Jan 27, 2026

    Choose a reason for hiding this comment

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

    The PR description mentions adding error handling in plugin_hub.py for OSError during WebSocket accept, but those changes are not present in this diff. Instead, the approach taken is to suppress asyncio ERROR logs globally for Windows. While this suppresses the noise, it also suppresses ALL asyncio ERROR logs on Windows, not just the WinError 64 from WebSocket accept. A more targeted approach would be to add try-except handling in the on_connect method of plugin_hub.py (lines 79-85) to catch and log OSError at an appropriate level, rather than suppressing all asyncio errors globally.

    Copilot uses AI. Check for mistakes.
    Comment on lines +16 to +20
    """
    Robustly normalize a vector parameter to [x, y, z] format.
    Handles: list, tuple, JSON string.
    Returns (parsed_vector, error_message). If error_message is set, parsed_vector is None.
    """
    Copy link

    Copilot AI Jan 27, 2026

    Choose a reason for hiding this comment

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

    The docstring states this function "Handles: list, tuple, JSON string" but the implementation in manage_gameobject.py (lines 16-67) also handles comma-separated strings as a legacy feature. Since this version doesn't include that legacy support, the docstring is accurate. However, this creates an inconsistency in user experience where the same vector parameter behaves differently across tools. Consider documenting this limitation or adding the same legacy support for consistency.

    Copilot uses AI. Check for mistakes.
    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