Skip to content

Conversation

@dsarno
Copy link
Owner

@dsarno dsarno commented Jan 4, 2026

Motivation

  • Prevent long (~10s) stalls caused by treating generic hint="retry" responses as full Unity reloads.
  • Limit blocking wait windows in stdio/TCP and HTTP/WebSocket paths so tools get a fast retry signal instead of lengthy server-side sleeps.
  • Make retry classification accurate so unrelated conditions like tests_running do not trigger reload wait loops.
  • Ensure refresh_unity does not mark reload/no-session retries as recovered-from-disconnect.

Description

  • Replace the broad _is_reloading_response logic with _extract_response_reason and a stricter _is_reloading_response that only treats explicit reloading reasons or messages containing "reload" as reloads.
  • Add an environment-configurable bounded wait for stdio/TCP reloads via UNITY_MCP_RELOAD_MAX_WAIT_S and micro-retry clamping (50–250ms), with debug logging and a fallback MCPResponse busy/retry payload when the budget is exceeded.
  • Cap plugin session resolution waits in PluginHub._resolve_session_id using UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S and return a retryable busy response (data.reason: "no_unity_session") instead of waiting the previous long default and raising.
  • Update refresh_unity to inspect response.data.reason and avoid setting recovered_from_disconnect=True for "reloading" or "no_unity_session" retry responses.

Testing

  • No automated tests were run as part of this change.
  • Changes were applied and committed to the repository successfully.
  • Recommended follow-ups include unit tests for send_command_with_retry and PluginHub._resolve_session_id to validate fast-fail behavior and classification (not included here).

Codex Task

Summary by CodeRabbit

  • Bug Fixes

    • Improved disconnect and retry handling with better response reason extraction.
    • Enhanced Unity reload handling with configurable wait windows and timeout protection.
  • Improvements

    • Added async command execution with built-in retry logic for better reliability.
    • Improved session resolution with bounded wait times and fast-fail detection.
    • Enhanced error messages for Unity session unavailability scenarios.

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

dsarno added 19 commits January 2, 2026 11:56
… stall prevention

- Add TestRunnerNoThrottle.cs: Sets editor to 'No Throttling' mode during test runs
  with SessionState persistence across domain reload
- Add run_tests_async and get_test_job tools for non-blocking test execution
- Add TestJobManager for async test job tracking with progress monitoring
- Add ForceSynchronousImport to all AssetDatabase.Refresh() calls to prevent stalls
- Mark DomainReloadResilienceTests as [Explicit] with documentation explaining
  the test infrastructure limitation (internal coroutine waits vs MCP socket polling)
- MCP workflow is unaffected - socket messages provide external stimulus that
  keeps Unity responsive even when backgrounded
- Remove unused Newtonsoft.Json.Linq import from TestJobManager
- Add throttling to SessionState persistence (once per second) to reduce overhead
- Critical job state changes (start/finish) still persist immediately
- Fix duplicate XML summary tag in DomainReloadResilienceTests
- Add run_tests_async and get_test_job to main README tools list
- Document background stall limitation for domain reload tests in DEV readme
Run [Explicit] domain_reload tests in their own job using -testCategory
Combines into single job with two test steps to reuse cached Library
- Fix TOCTOU race in TestJobManager.StartJob (single lock scope for check-and-set)
- Store TestRunnerApi reference with HideAndDontSave to prevent GC/serialization issues
- run_tests_async is now marked as preferred for long-running suites
- run_tests description notes it blocks and suggests async alternative
…sults

- manage_asset, manage_gameobject, manage_scene now check preflight return
  value and propagate busy/retry signals to clients (fixes Sourcery #1)
- TestJobManager.FinalizeCurrentJobFromRunFinished now sets job status to
  Failed when resultPayload.Failed > 0, not always Succeeded (fixes Sourcery #2)
When 'Force fresh server install' is enabled, uvx uses --no-cache --refresh
which rebuilds the package and takes significantly longer to start.

- Increase timeout from 10s to 45s when dev mode is enabled
- Add informative log message explaining the longer startup time
- Show actual timeout value in warning message
Apply same logic as FinalizeCurrentJobFromRunFinished: check result.Failed > 0
to correctly mark jobs as Failed when tests fail, even in the fallback path
when RunFinished callback is not delivered.
@coderabbitai
Copy link

coderabbitai bot commented Jan 4, 2026

Walkthrough

The pull request adds enhanced retry and reload handling across the Unity MCP transport layer. Changes include a new meta file for a configurator, a response reason extraction utility in unity_connection.py, environment-driven configuration for reload max-wait bounds, an async variant of send_command_with_retry, a new NoUnitySessionError exception in plugin_hub.py, and bounded session resolution/readiness logic.

Changes

Cohort / File(s) Summary
Meta file
MCPForUnity/Editor/Clients/Configurators/CherryStudioConfigurator.cs.meta
Standard Unity MonoImporter meta file added with default configuration.
Response reason extraction
Server/src/transport/legacy/unity_connection.py
Introduces _extract_response_reason() to normalize reason extraction from MCPResponse or dict payloads; replaces inline reloading logic. Refactors _is_reloading_response() to use the new utility.
Reload handling & telemetry
Server/src/transport/legacy/unity_connection.py
Adds UNITY_MCP_RELOAD_MAX_WAIT_S environment variable with clamping [0, 30]; tracks wait start time, per-retry decisions, and outcome logging; inspects retry_after_ms in responses with bounds and diagnostics; returns MCPResponse on budget exceeded.
Async command retry
Server/src/transport/legacy/unity_connection.py
Introduces async_send_command_with_retry() to run blocking retry logic in a thread pool with same return type as sync version.
Session error handling
Server/src/transport/plugin_hub.py
Adds NoUnitySessionError exception for signaling absent Unity plugins; caught in send_command_for_instance() to return retryable MCPResponse.
Bounded session resolution
Server/src/transport/plugin_hub.py
Replaces max_retries deadline with UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S (clamped [0, 30], default 2.0); tightens retry sleep to [0.05, 0.25]s; updates log messages with instance context.
Session readiness probe
Server/src/transport/plugin_hub.py
Adds fast-path readiness check using UNITY_MCP_SESSION_READY_WAIT_SECONDS [0, 30] for non-ping commands to enable fast-fail behavior.
Reason extraction usage
Server/src/services/tools/refresh_unity.py
Imports and uses _extract_response_reason() to derive reason from failure responses; conditions recovered_from_disconnect on reason value, excluding {"reloading", "no_unity_session"}.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • msanatan

Poem

🐰 New paths to retry, fresh bounds to confess,
Reasons extracted, reloads compressed,
Async hops forward with threads they possess,
Sessions resolved with a hop and a test,
The warren rejoices—reliability's blessed! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 directly and concisely summarizes the main change: bounding Unity reload/session waits and refining retry handling logic, which is the core objective of the pull request.
✨ 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.

@dsarno dsarno changed the base branch from feat/unity-readiness-v2 to main January 4, 2026 00:33
@greptile-apps
Copy link

greptile-apps bot commented Jan 4, 2026

Greptile Summary

This PR addresses a critical performance issue where generic hint="retry" responses were incorrectly treated as full Unity reloads, causing ~10s stalls. The solution introduces three key improvements:

  1. Refined Retry Classification: Replaces the broad _is_reloading_response logic with _extract_response_reason() that distinguishes between "reloading" (actual editor reload) and other retry conditions like "no_unity_session" or generic retries.

  2. Bounded Wait Windows: Both stdio/TCP and HTTP/WebSocket paths now enforce configurable time limits (UNITY_MCP_RELOAD_MAX_WAIT_S default 2.0s, UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S default 2.0s) with micro-retry clamping (50-250ms bounds) to ensure fast client-side retry signals instead of lengthy server-side sleeps.

  3. Smart Disconnect Recovery: The refresh_unity tool now conditionally sets recovered_from_disconnect=True by inspecting response.data.reason, avoiding false "recovery" signals for unrelated retry conditions.

The changes maintain backward compatibility by keeping hint="retry" as a supported field while introducing the more specific data.reason classification system. Error handling is defensive with proper type checking and exception handlers for environment variable parsing.

Confidence Score: 5/5

  • This PR is safe to merge. The changes introduce well-scoped improvements to timeout handling with proper defensive coding, and all logic changes maintain backward compatibility while fixing the identified performance issue.
  • The changes are focused and well-implemented. The refactoring of _is_reloading_response() into _extract_response_reason() cleanly separates concerns and enables more accurate retry classification. Environment variable parsing includes defensive exception handling with sensible defaults. The bounded wait logic properly tracks elapsed time and includes comprehensive debug logging. Type checking is thorough throughout (checking isinstance before accessing dict keys). The three modified files work together coherently to reduce wait times while maintaining robust error handling. No logical errors, security vulnerabilities, or backward compatibility issues detected.
  • No files require special attention. All changes are well-scoped and properly implemented.

Sequence Diagram

sequenceDiagram
    participant Client
    participant send_command_with_retry
    participant Unity as Unity Editor
    participant _extract_response_reason

    Client->>send_command_with_retry: send_command_with_retry(command)
    send_command_with_retry->>send_command_with_retry: Get max_wait_s from UNITY_MCP_RELOAD_MAX_WAIT_S (2.0s)
    send_command_with_retry->>Unity: conn.send_command()
    Unity-->>send_command_with_retry: response (reloading)
    send_command_with_retry->>_extract_response_reason: Extract reason from response
    _extract_response_reason-->>send_command_with_retry: "reloading"
    
    alt Is Reloading AND within time budget
        loop While _is_reloading_response(response) AND retries < max_retries AND elapsed < max_wait_s
            send_command_with_retry->>send_command_with_retry: Clamp sleep to [50-250]ms
            send_command_with_retry->>send_command_with_retry: Sleep(sleep_ms)
            send_command_with_retry->>Unity: conn.send_command()
            Unity-->>send_command_with_retry: response (still reloading)
            send_command_with_retry->>_extract_response_reason: Update reason
        end
    else Time budget exceeded
        send_command_with_retry-->>Client: MCPResponse(hint=retry, data.reason=reloading)
    else Reload completed
        send_command_with_retry-->>Client: Final response from Unity
    end
Loading

Copy link

@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: 0

🧹 Nitpick comments (4)
Server/src/services/tools/refresh_unity.py (1)

50-56: Minor redundancy in type check.

Line 51 has a redundant isinstance(data, dict) check since line 50 already guarantees data is a dict (either from response.get("data") if it's a dict, or {}).

The recovery logic correctly avoids setting recovered_from_disconnect = True for reload or no-session scenarios.

🔎 Suggested simplification
         data = response.get("data") if isinstance(response.get("data"), dict) else {}
-        reason = data.get("reason") if isinstance(data, dict) else None
+        reason = data.get("reason")
Server/src/transport/plugin_hub.py (1)

364-370: Blind exception catch is acceptable here but could be narrower.

The static analysis flags except Exception (BLE001). While this is typically discouraged, here it's a reasonable defensive pattern for environment variable parsing where the only goal is to fall back to a default. However, narrowing to ValueError would be more precise since float() only raises ValueError for invalid strings.

🔎 Narrower exception handling
         try:
             max_wait_s = float(
                 os.environ.get("UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S", "2.0"))
-        except Exception:
+        except (TypeError, ValueError):
             max_wait_s = 2.0
Server/src/transport/legacy/unity_connection.py (2)

754-759: Same blind exception pattern as plugin_hub.py.

For consistency, consider narrowing the exception type here as well.

🔎 Narrower exception handling
     try:
         max_wait_s = float(os.environ.get(
             "UNITY_MCP_RELOAD_MAX_WAIT_S", "2.0"))
-    except Exception:
+    except (TypeError, ValueError):
         max_wait_s = 2.0

850-860: Consider preserving more error context.

The except Exception on line 859 catches all exceptions and converts them to a generic MCPResponse(success=False, error=str(e)). This loses exception type information that might be useful for callers to distinguish between:

  • Connection errors (retryable)
  • Configuration errors (not retryable)
  • Unexpected bugs (should surface)

Consider either re-raising certain exception types or including the exception class name in the error message.

🔎 Include exception type in error
     except Exception as e:
-        return MCPResponse(success=False, error=str(e))
+        return MCPResponse(success=False, error=f"{type(e).__name__}: {e}")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46b844a and 600e353.

📒 Files selected for processing (4)
  • MCPForUnity/Editor/Clients/Configurators/CherryStudioConfigurator.cs.meta
  • Server/src/services/tools/refresh_unity.py
  • Server/src/transport/legacy/unity_connection.py
  • Server/src/transport/plugin_hub.py
🧰 Additional context used
🧬 Code graph analysis (1)
Server/src/transport/plugin_hub.py (1)
Server/tests/integration/test_helpers.py (1)
  • model_dump (10-13)
🪛 Ruff (0.14.10)
Server/src/transport/legacy/unity_connection.py

757-757: Do not catch blind exception: Exception

(BLE001)

Server/src/transport/plugin_hub.py

368-368: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (7)
MCPForUnity/Editor/Clients/Configurators/CherryStudioConfigurator.cs.meta (1)

1-11: LGTM!

The Unity metadata file is properly formatted with the correct structure and standard MonoImporter configuration. The guid is valid and all required fields are present.

Server/src/transport/plugin_hub.py (3)

453-468: LGTM - Graceful handling for unavailable Unity session.

The error handling correctly catches the "no Unity plugins connected" case and returns a structured retry response with reason: "no_unity_session". This aligns with the PR's goal of providing fast retry signals instead of blocking.

Note: String-based exception matching (line 456) is fragile but acceptable here since both the raise and catch are in the same file. Consider defining a custom exception class in a future refactor for better type safety.


475-501: LGTM - Bounded readiness probe for fast-path commands.

The ping-based readiness check correctly:

  1. Excludes ping itself to avoid recursion
  2. Clamps the wait window to a reasonable 0–30s range
  3. Uses the while...else pattern to return a retry hint when the deadline expires

This prevents long blocking waits when Unity is unfocused or reloading.


414-427: Good use of parameterized logging.

The switch from f-strings to %-style logging is a best practice—it defers string formatting until the log record is actually emitted, avoiding unnecessary work when the log level is filtered out.

Server/src/transport/legacy/unity_connection.py (3)

689-714: Well-structured reason extraction logic.

The _extract_response_reason function correctly handles:

  • MCPResponse objects with data.reason attribute
  • Raw dict payloads with state or data.reason fields
  • Fallback to checking message/error for "reload" substring

This centralizes the logic that was previously scattered across multiple checks.


763-799: LGTM - Enhanced retry loop with bounded waiting.

The retry logic correctly:

  1. Tracks wait start time and enforces max_wait_s budget
  2. Extracts retry_after_ms from response data for dynamic backoff
  3. Clamps sleep duration to [50ms, 250ms] to prevent both busy-spinning and excessive delays
  4. Provides detailed debug logging for troubleshooting

Minor observation: reason is extracted on line 764 and 799 for logging, while _is_reloading_response on line 765 re-extracts internally. This is slightly redundant but keeps the code readable.


801-825: LGTM - Proper handling when reload wait budget is exceeded.

When the bounded wait expires while Unity is still reloading, the function correctly returns an MCPResponse with:

  • hint: "retry" for client-side retry logic
  • data.reason: "reloading" for reason-based flow in callers like refresh_unity
  • retry_after_ms clamped to [50, 250] for reasonable backoff

This prevents the ~10s stalls mentioned in the PR objectives.

Address code review feedback:

- Catch ValueError specifically (instead of broad Exception) when parsing
  UNITY_MCP_RELOAD_MAX_WAIT_S, UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S, and
  UNITY_MCP_SESSION_READY_WAIT_SECONDS, with logging for easier diagnosis
  of misconfiguration

- Normalize reason values to lowercase in _extract_response_reason() to
  avoid case-sensitive mismatches in comparisons

- Simplify refresh_unity.py by removing redundant isinstance check and
  reusing _extract_response_reason instead of duplicating reason parsing
Copy link

@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: 3

🧹 Nitpick comments (1)
Server/src/transport/legacy/unity_connection.py (1)

772-833: LGTM! Bounded retry logic is well-structured with comprehensive logging.

The implementation correctly:

  • Initializes the wait timer on the first reload response
  • Enforces the max_wait_s budget by checking elapsed time
  • Respects Unity's retry_after_ms hints while clamping sleep to [50, 250]ms
  • Provides detailed debug logging at each stage (start, retry, completion, budget exceeded)
  • Returns a structured MCPResponse when the wait budget is exceeded

Optional: Consider rounding instead of truncating delay_ms.

Line 796 converts delay_ms to int(), which truncates rather than rounds. For consistency with typical time handling:

-        sleep_ms = max(50, min(int(delay_ms), 250))
+        sleep_ms = max(50, min(round(delay_ms), 250))

This is a minor refinement and unlikely to impact behavior materially.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 600e353 and 4e1b090.

📒 Files selected for processing (3)
  • Server/src/services/tools/refresh_unity.py
  • Server/src/transport/legacy/unity_connection.py
  • Server/src/transport/plugin_hub.py
🧰 Additional context used
🧬 Code graph analysis (2)
Server/src/transport/legacy/unity_connection.py (1)
Server/src/transport/plugin_hub.py (1)
  • send_command (133-210)
Server/src/services/tools/refresh_unity.py (1)
Server/src/transport/legacy/unity_connection.py (2)
  • async_send_command_with_retry (837-869)
  • _extract_response_reason (689-719)
🔇 Additional comments (5)
Server/src/services/tools/refresh_unity.py (2)

13-13: LGTM!

The imports are correctly used: async_send_command_with_retry is passed to the transport layer and _extract_response_reason is used to extract failure reasons for refined retry handling.


50-55: LGTM! Improved retry classification.

The refined logic correctly distinguishes true disconnect recovery from transient busy states ("reloading", "no_unity_session"). This prevents false-positive recovery signals and aligns with the PR's goal of accurate retry classification.

Server/src/transport/plugin_hub.py (2)

405-443: LGTM! Well-structured bounded wait with good observability.

The bounded waiting logic is correctly implemented with clear debug logging that includes instance context. The wait deadline is enforced properly, and the logging will help diagnose session resolution issues.


479-509: LGTM! Fast-fail readiness probe is well-implemented.

The bounded readiness probe correctly:

  • Clamps UNITY_MCP_SESSION_READY_WAIT_SECONDS to [0, 30]
  • Handles invalid configuration values gracefully
  • Uses ping to verify main-thread readiness
  • Returns a structured retry response when the session isn't ready
Server/src/transport/legacy/unity_connection.py (1)

689-728: LGTM! Robust reason extraction with good type safety.

The _extract_response_reason function correctly:

  • Handles both MCPResponse and raw dict payloads
  • Safely extracts reasons from nested data.reason fields
  • Falls back to checking message/error text for "reload" keywords
  • Returns lowercase strings for case-insensitive comparison by callers
  • Uses safe attribute access and None-coalescing

The _is_reloading_response wrapper provides a clean boolean interface.

- Add upper bound (30s) to UNITY_MCP_RELOAD_MAX_WAIT_S to prevent
  misconfiguration from causing excessive waits
- Add upper bound (30s) to UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S for
  consistency with readiness probe
- Introduce NoUnitySessionError custom exception to replace fragile
  string matching in send_command_for_instance

Addresses code review feedback for bounded retry policy PR.
Copy link

@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: 0

🧹 Nitpick comments (2)
Server/src/transport/legacy/unity_connection.py (2)

791-796: Consider handling MCPResponse for retry_after_ms extraction.

The retry_after_ms extraction only handles dict responses. Since conn.send_command can return MCPResponse (line 242), responses of that type won't have their data.retry_after_ms inspected here.

While this may not be an issue in practice (Unity responses are dicts, MCPResponse is for local errors), aligning with _extract_response_reason's dual handling would be more consistent.

🔎 Optional: extend retry_after_ms extraction to MCPResponse
         delay_ms = retry_ms
-        if isinstance(response, dict):
-            retry_after = response.get("retry_after_ms")
-            if retry_after is None and isinstance(response.get("data"), dict):
-                retry_after = response["data"].get("retry_after_ms")
-            if retry_after is not None:
-                delay_ms = int(retry_after)
+        retry_after = None
+        if isinstance(response, dict):
+            retry_after = response.get("retry_after_ms")
+            if retry_after is None and isinstance(response.get("data"), dict):
+                retry_after = response["data"].get("retry_after_ms")
+        elif isinstance(response, MCPResponse):
+            data = getattr(response, "data", None)
+            if isinstance(data, dict):
+                retry_after = data.get("retry_after_ms")
+        if retry_after is not None:
+            delay_ms = int(retry_after)

860-870: Broad exception swallowing may hide important errors.

The sync send_command_with_retry propagates exceptions (e.g., ConnectionError, ValueError), but this async wrapper catches all exceptions and converts them to MCPResponse. This asymmetry could mask programming errors or connection issues that callers might want to handle differently.

🔎 Suggested: be more selective about caught exceptions
     try:
         import asyncio  # local import to avoid mandatory asyncio dependency for sync callers
         if loop is None:
             loop = asyncio.get_running_loop()
         return await loop.run_in_executor(
             None,
             lambda: send_command_with_retry(
                 command_type, params, instance_id=instance_id, max_retries=max_retries, retry_ms=retry_ms),
         )
-    except Exception as e:
+    except (ConnectionError, TimeoutError, OSError) as e:
+        # Convert recoverable transport errors to MCPResponse
         return MCPResponse(success=False, error=str(e))
+    # Let programming errors (ValueError, TypeError, etc.) propagate

Alternatively, if broad catching is intentional for resilience, consider logging at warning/error level so issues are observable.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e1b090 and f4d2637.

📒 Files selected for processing (2)
  • Server/src/transport/legacy/unity_connection.py
  • Server/src/transport/plugin_hub.py
🧰 Additional context used
🧬 Code graph analysis (1)
Server/src/transport/plugin_hub.py (1)
Server/tests/integration/test_helpers.py (3)
  • warning (39-40)
  • error (46-47)
  • model_dump (10-13)
🪛 Ruff (0.14.10)
Server/src/transport/plugin_hub.py

451-451: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (10)
Server/src/transport/legacy/unity_connection.py (4)

689-719: LGTM! Clean extraction logic with proper normalization.

The helper correctly extracts and normalizes reason strings from both MCPResponse objects and raw dicts. The lowercase normalization enables consistent case-insensitive comparisons.


722-728: LGTM! Improved specificity over the previous broad check.

The refactor to use _extract_response_reason makes the reloading detection more explicit and maintainable. Only "reloading" reasons or messages containing "reload" are now treated as reloads, avoiding misclassification of unrelated conditions.


759-769: Environment variable handling is well-implemented.

Good defensive coding with ValueError handling, warning logs for invalid values, and the [0, 30] clamp to prevent misconfiguration.


820-828: Structured retry response on budget exhaustion looks good.

The fallback MCPResponse correctly signals a retryable state with hint="retry" and includes reason and bounded retry_after_ms in data, enabling downstream consumers (like refresh_unity) to handle these responses appropriately.

Server/src/transport/plugin_hub.py (6)

37-39: Good addition of custom exception type.

This replaces fragile string matching with a proper exception type, making the error handling in send_command_for_instance more robust and maintainable.


368-381: Environment variable handling is well-implemented and consistent.

Good defensive coding with:

  • ValueError handling with warning logs
  • Upper bound clamping to [0, 30] to prevent misconfiguration
  • Sleep clamping to [0.05, 0.25] aligns with unity_connection.py's 50-250ms range

410-436: LGTM! Improved deadline-based waiting with clear logging.

Using time.monotonic() for deadline calculation is correct for measuring elapsed time. The logging clearly indicates wait start, restoration, and the instance context.


443-451: Correctly raises custom exception after wait exhaustion.

The static analysis hint (TRY003) about message placement is a minor style concern—the message is descriptive and not excessively long. The current approach is acceptable.


462-475: Clean exception handling with structured retry response.

The NoUnitySessionError handler correctly returns a retryable MCPResponse with reason: "no_unity_session" and retry_after_ms: 250, enabling refresh_unity to avoid misclassifying this as a recovered disconnect.


485-490: Consistent env var validation pattern.

The error handling mirrors the pattern used for UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S, maintaining consistency across the codebase.

@dsarno dsarno closed this Jan 4, 2026
@dsarno dsarno deleted the codex/implement-bounded-retry-policy-for-unity branch January 4, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants