Skip to content

fix(editor): survive domain reloads in HTTP auto-start and reload-resume (#1229)#1234

Draft
Scriptwonder wants to merge 2 commits into
CoplayDev:betafrom
Scriptwonder:fix/issue-1229-reload-resume-race
Draft

fix(editor): survive domain reloads in HTTP auto-start and reload-resume (#1229)#1234
Scriptwonder wants to merge 2 commits into
CoplayDev:betafrom
Scriptwonder:fix/issue-1229-reload-resume-race

Conversation

@Scriptwonder

@Scriptwonder Scriptwonder commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1229. Full credit to @jtpistella for the precise two-part root-cause diagnosis in the issue — both mechanisms verified line-for-line before writing this.

Root causes (verified at beta HEAD)

  1. Auto-start dead for the whole session when startup includes a compile: HttpAutoStartHandler's cctor set the reload-surviving SessionState latch before registering the EditorApplication.delayCall — a domain reload wipes the pending delegate, and the latch then short-circuits every later domain load.
  2. Reload-resume permanently lost at multi-pass compiles (e.g. importing a script-bearing asset package): HttpBridgeReloadHandler consumed the one-shot resume flag before the deferred (delayCall) resume ran, and the next beforeAssemblyReload deleted it again because the bridge wasn't running.

Fix

  • Update ticks instead of delayCall: the [InitializeOnLoad] cctor re-arms an EditorApplication.update tick on every domain load, and the latch is written only when the deferred work actually dispatches. Services-not-ready frames retry (bounded per domain); the common case (auto-start off, nothing pending) skips the subscription entirely.
  • Resume flag moved EditorPrefs → SessionState and it now persists until the resume succeeds, is cancelled, or exhausts its retries — instead of being consumed at boundaries where the bridge is down. EditorPrefs was also per-user machine-global (two concurrently open editors could consume/delete each other's pending resume) and survived crashes (a stale flag could resurrect the bridge on the next launch without opt-in). A once-per-session migration deletes the legacy key.
  • TransportManager.StartAsync serialized per mode: concurrent starts coalesce onto one in-flight attempt. Manual Connect, reload-resume, and auto-start all flow through it, and WebSocketTransportClient.StartAsync tears down a live connection first — previously two interleaved starts could bounce a just-established session.
  • Connect-pending marker: a reload that kills the in-flight connect phase no longer strands auto-start; the next domain load finishes connect-only (never re-spawns — StartLocalHttpServer stops a still-booting server first). IServerManagementService.HasManagedServerLaunchHandle answers the "can we watch the launch process die?" question live; without a handle the wait polls to the 5-minute hard cap instead of fail-fasting.
  • Busy gate uses EditorStateCache.GetActualIsCompiling (now internal, reflection bound once as a delegate): raw EditorApplication.isCompiling stays true for a whole play session under Recompile-After-Finished-Playing (EditorApplication.isCompiling False Positive in Play Mode #549), which would otherwise block resume until play exits.
  • User-intent cancel paths (Connect, End Session, transport switch, orphan cleanup) go through named seams (CancelPendingResume / CancelPendingReconnect) that also abort an in-flight retry loop.

Intended behavior changes

  • The HTTP bridge no longer auto-resumes across an editor restart unless Auto-Start on Load is enabled (the old EditorPrefs flag could trigger an unrequested launch-time resume after a crash).
  • An invalid resume no longer replays a ~49s retry loop at every later reload: exhaustion erases the flag (matches the stdio sibling).
  • A reload-interrupted auto-start now recovers (connect-only) instead of silently dying.

Verification

  • Compile-only matrix via tools/check-unity-versions.sh (2021.3.45f2 floor passes locally; CI covers the rest — the floor caught that CompilationPipeline.isCompiling needs reflection there).
  • 23 EditMode tests added (HttpBridgeReloadHandlerTests, HttpAutoStartHandlerTests, TransportManagerTests), including a scenario test replaying the exact multi-pass-compile sequence from the issue, and a pin on the StartAsync coalescing contract. Full EditMode suite: 1002 passed / 0 failed locally.
  • Note: the seam tests cover the decision/flag logic; the "delegate re-arms across a real domain reload" wiring can't be unit-tested on UTF 1.1 — verified manually. A repro pass on Windows per the issue's setup would be a welcome review step.

Relationship to other open PRs (checked before filing)

Known follow-ups (deliberately out of scope)

  • stdio sibling has the same defect class: StdioBridgeReloadHandler.cs:65 (flag deleted at a bridge-down boundary — primary) and :133 (delayCall deferral — secondary). Kept out to respect fix: harden localhost resolution and reload transport resilience on Windows #688's centralized stdio teardown sequencing; same pattern applies.
  • Remaining stdio copies of the isCompiling reflection probe can now consolidate onto EditorStateCache.GetActualIsCompiling.
  • The manual Start Server path (McpConnectionSection.TryAutoStartSessionAsync) lacks the same reload survival; candidate for the same connect-pending pattern.

Summary by CodeRabbit

  • New Features

    • Added reload-safe handling for HTTP auto-start and reconnect flow, including resume tracking across editor reloads.
    • Improved transport start behavior so repeated start requests share the same in-flight attempt.
  • Bug Fixes

    • Prevented resume/connect flags from leaking between editor sessions.
    • Cleared pending reconnect/resume state when users change transport or disconnect.
    • Updated editor reload behavior to better handle busy states and recovery timing.
  • Tests

    • Added coverage for auto-start, reconnect, reload-resume, and concurrent transport start scenarios.

…ume (CoplayDev#1229)

Auto-start died for the whole session whenever startup included a compile:
the ctor latched SessionState before the delayCall ran, and the reload wiped
the delayCall. Reload-resume died at multi-pass compiles: the one-shot flag
was consumed before the deferred (delayCall) resume ran, and the next
boundary deleted it again.

- Replace delayCall with EditorApplication.update ticks that the
  [InitializeOnLoad] ctor re-arms on every domain load; latch only when the
  deferred work actually dispatches, retry (bounded per domain) while editor
  services are still initializing, and skip the subscription entirely in the
  common case where auto-start is off and nothing is pending.
- Move the resume flag from EditorPrefs (per-user machine-global, survives
  crashes, leaks across concurrently open editors) to SessionState; keep it
  until the resume succeeds, is cancelled, or exhausts its retries, instead
  of consuming it at boundaries where the bridge is down. Manual Connect,
  End Session, transport switch, and orphan cleanup cancel a pending resume
  through a named seam (CancelPendingResume), which also aborts an in-flight
  retry loop; exhaustion erases the flag so later reloads don't replay 49s
  failure loops.
- Serialize TransportManager.StartAsync per mode: concurrent starts coalesce
  onto one in-flight attempt, so a manual Connect can no longer race the
  resume/auto-start loops into bouncing a just-established session
  (WebSocketTransportClient.StartAsync tears down a live connection first).
- A SessionState connect-pending marker lets the next domain load finish an
  auto-start whose connect phase a reload killed — connect-only, never
  re-spawning (StartLocalHttpServer stops a still-booting server first).
  Whether a launch-process handle exists is now answered live by
  ServerManagementService.HasManagedServerLaunchHandle; without one (post-
  reload, or an externally started server) the wait polls to the 5-minute
  hard cap instead of fail-fasting.
- Busy gate uses EditorStateCache.GetActualIsCompiling (now internal, with
  the CompilationPipeline reflection bound once as a delegate): raw
  isCompiling stays true all play session under
  Recompile-After-Finished-Playing (CoplayDev#549).
- One-time (per session) migration deletes the legacy EditorPrefs flag.

The stdio sibling has the same defect class (StdioBridgeReloadHandler.cs:65
delete-when-not-running, :133 delayCall) — follow-up, kept out of scope here,
along with the remaining stdio copies of the isCompiling probe.
@Scriptwonder Scriptwonder added the bug Something isn't working label Jul 4, 2026
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8db54ef3-ed8f-40ff-8fc2-01b913b77874

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Reworks HTTP auto-start and reload-resume handling to survive Unity domain reloads by moving state from EditorPrefs to SessionState and replacing delayCall scheduling with an EditorApplication.update tick state machine. Adds a managed-launch-handle signal, coalesces TransportManager start calls, updates connection UI cancellation, and adds tests.

Changes

Reload-safe auto-start and resume

Layer / File(s) Summary
Managed launch handle and compile-state signals
MCPForUnity/Editor/Services/IServerManagementService.cs, MCPForUnity/Editor/Services/ServerManagementService.cs, MCPForUnity/Editor/Services/EditorStateCache.cs
Adds HasManagedServerLaunchHandle property and caches a reflection-bound delegate for CompilationPipeline.isCompiling, exposing GetActualIsCompiling internally.
HttpAutoStartHandler tick state machine
MCPForUnity/Editor/Services/HttpAutoStartHandler.cs
Replaces delayCall-based startup with a persistent update tick, TickDecision enum, TickCore, TryBeginAutoStart/TryBeginReconnect, and a new ReconnectAsync that uses the managed-launch-handle signal.
HttpBridgeReloadHandler SessionState-based resume
MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs
Moves the resume flag to SessionState, migrates the legacy EditorPrefs key, splits reload handling into core methods, adds IsResumePending/CancelPendingResume, and updates retry scheduling.
TransportManager start coalescing
MCPForUnity/Editor/Services/Transport/TransportManager.cs
Caches in-flight per-mode start tasks so concurrent StartAsync calls share one attempt via a new StartCoreAsync.
Connection UI cancellation wiring and prefs cleanup
MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs, MCPForUnity/Editor/Constants/EditorPrefKeys.cs, MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs
Replaces ResumeHttpAfterReload EditorPrefs deletion with calls to CancelPendingResume/CancelPendingReconnect, and removes the now-unused constant and its known-type entry.
Tests for tick decisions, resume flow, and transport coalescing
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/HttpAutoStartHandlerTests.cs, .../HttpBridgeReloadHandlerTests.cs, .../TransportManagerTests.cs, .../TestUtilities.cs, .../Windows_Characterization.cs, *.meta
Adds EditMode tests for tick decisions, reconnect gating, reload core handling, retry/resume behavior, multi-pass compile scenarios, and transport start coalescing, plus a FakeTransportClient test double.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EditorApplication
  participant HttpAutoStartHandler
  participant SessionState
  participant HttpBridgeReloadHandler
  participant TransportManager

  EditorApplication->>HttpAutoStartHandler: domain load / update tick
  HttpAutoStartHandler->>SessionState: check SessionInitKey / ConnectPendingKey
  HttpAutoStartHandler->>HttpBridgeReloadHandler: check IsResumePending
  alt Resume pending
    HttpBridgeReloadHandler->>HttpBridgeReloadHandler: ResumeTick (editor idle)
    HttpBridgeReloadHandler->>TransportManager: ResumeHttpWithRetriesAsync -> StartAsync(Http)
    HttpBridgeReloadHandler->>SessionState: erase ResumeSessionKey on success/exhaustion
  else Auto-start decision
    HttpAutoStartHandler->>HttpAutoStartHandler: TickCore -> ShouldStart or ShouldReconnect
    HttpAutoStartHandler->>TransportManager: AutoStartAsync or ReconnectAsync
    HttpAutoStartHandler->>SessionState: set/erase ConnectPendingKey
  end
Loading

Possibly related issues

Possibly related PRs

  • CoplayDev/unity-mcp#402: Modifies reload/resume logic around HttpBridgeReloadHandler and mode-aware TransportManager start/stop semantics that overlap with this PR's SessionState-based resume changes.
  • CoplayDev/unity-mcp#530: Modifies resume-flag management in McpConnectionSection/reload handlers for ResumeHttpAfterReload/ResumeStdioAfterReload, overlapping with this PR's cancellation wiring.
  • CoplayDev/unity-mcp#923: Introduces the original HttpAutoStartHandler.cs editor-load auto-start handler that this PR rewrites into a tick state machine.

Suggested reviewers: dsarno

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly reflects the main change: fixing HTTP auto-start and reload-resume survival across domain reloads.
Description check ✅ Passed It covers the bug, root causes, fix, verification, related issues, and follow-ups, even though it does not use the exact template headings.
Linked Issues check ✅ Passed The changes match #1229 by moving deferred work to update ticks, persisting resume state in SessionState, coalescing starts, and adding recovery tests.
Out of Scope Changes check ✅ Passed No clearly unrelated changes stand out; the edits support the reload-resume and auto-start fix plus the accompanying tests and cleanup.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Scriptwonder added a commit to Scriptwonder/unity-mcp that referenced this pull request Jul 4, 2026
CoplayDev#1207)

The orphaned-session detector ended an active session on a SINGLE stale
reachability reading, and the reading came from a lone 50ms TCP connect
cached for 0.75s — trivially false-negative on a machine busy with test
runs or domain reloads. Evidence bundles in CoplayDev#1207 show 13 teardowns and
147 socket closures in one session from exactly this loop, wedging the
bridge in no_unity_session churn until manual recovery.

- Require 3 consecutive failed polls (0.75s cadence) before declaring a
  session orphaned; probe readings taken while the editor is compiling or
  importing don't count toward teardown, and detection is skipped entirely
  while busy.
- Raise the probe's connect wait 50ms -> 250ms, as an overall budget shared
  across candidate hosts so the worst-case main-thread wait cannot multiply.
- Honor UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S above 20s (ceiling now 120s;
  default unchanged): the old ceiling equalled the default, silently
  neutering the documented escape hatch for projects whose reloads or test
  boundaries legitimately exceed 20s. Same treatment for
  UNITY_MCP_SESSION_READY_WAIT_SECONDS, and both now share one bounded
  env-read helper.

The remaining piece of CoplayDev#1207 (keepalive reload-awareness in
WebSocketTransportClient) is untouched here: the resume machinery reworked
in CoplayDev#1234 already covers reload boundaries, and the detector debounce
removes the dominant churn source dsarno identified.
@Scriptwonder Scriptwonder added the safe-to-test Triggers CI checks label Jul 4, 2026
@Scriptwonder

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs (1)

164-187: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Keep service-race failures inside the retry loop.

OnAfterAssemblyReloadCore() intentionally schedules resume when config reads race reload, but these pre-start checks can still throw before the attempt try/catch. That can kill the fire-and-forget task and leave ResumeSessionKey set with no retry scheduled.

Suggested fix
-                // Abort retries if the user switched transports while we were waiting.
-                if (!EditorConfigurationCache.Instance.UseHttpTransport)
-                {
-                    SessionState.EraseBool(ResumeSessionKey);
-                    return;
-                }
-
-                // Never bounce a session someone else established while we were waiting
-                // (WebSocketTransportClient.StartAsync tears down a live connection first).
-                if (MCPServiceLocator.TransportManager.IsRunning(TransportMode.Http))
-                {
-                    SessionState.EraseBool(ResumeSessionKey);
-                    return;
-                }
-
                 try
                 {
+                    // Abort retries if the user switched transports while we were waiting.
+                    if (!EditorConfigurationCache.Instance.UseHttpTransport)
+                    {
+                        SessionState.EraseBool(ResumeSessionKey);
+                        return;
+                    }
+
+                    // Never bounce a session someone else established while we were waiting.
+                    if (MCPServiceLocator.TransportManager.IsRunning(TransportMode.Http))
+                    {
+                        SessionState.EraseBool(ResumeSessionKey);
+                        return;
+                    }
+
                     bool started = await MCPServiceLocator.TransportManager.StartAsync(TransportMode.Http);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs` around lines 164 -
187, Keep service-race failures inside the retry loop: in
OnAfterAssemblyReloadCore, move the early transport/state checks that can throw
(such as UseHttpTransport and TransportManager.IsRunning) into the existing
try/catch around TransportManager.StartAsync, or wrap the whole resume attempt
block so any transient service lookup/race exception is caught and triggers the
retry scheduling path. Make sure SessionState.EraseBool(ResumeSessionKey) only
happens on confirmed success or intentional abort, so a thrown pre-start check
doesn’t cancel the fire-and-forget resume task without rescheduling.
MCPForUnity/Editor/Services/EditorStateCache.cs (1)

532-569: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don’t reflect CompilationPipeline.isCompiling as a public property. Unity 6000.3 doesn’t expose that member publicly, so this delegate will stay null and Play mode will fall back to EditorApplication.isCompiling, bringing back the false-positive busy state. Use the compilation started/finished events, or switch to a non-public lookup only if that member is present on the supported Unity range.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MCPForUnity/Editor/Services/EditorStateCache.cs` around lines 532 - 569, The
GetActualIsCompiling/PipelineIsCompiling path is reflecting
CompilationPipeline.isCompiling as a public property, but that member isn’t
publicly available on the newer Unity range so the delegate never binds. Update
CreatePipelineIsCompilingDelegate to use a supported signal instead, such as the
compilation started/finished events, or only fall back to a non-public lookup
when the member exists on the target Unity versions. Keep the change localized
to EditorStateCache.GetActualIsCompiling and its helper delegate setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@MCPForUnity/Editor/Services/Transport/TransportManager.cs`:
- Around line 53-61: Validate the transport mode before touching the coalescing
tasks in `TransportManager.StartAsync`; the current ternary treats every
non-`Http` value as `Stdio`, which can incorrectly reuse `_stdioStartTask` for
unsupported enum values. Refactor the lookup and assignment logic into a
`switch` on `mode` so only `TransportMode.Http` and `TransportMode.Stdio` map to
`_httpStartTask` and `_stdioStartTask`, and let invalid modes preserve the
unsupported-mode contract instead of returning an unrelated task.

---

Outside diff comments:
In `@MCPForUnity/Editor/Services/EditorStateCache.cs`:
- Around line 532-569: The GetActualIsCompiling/PipelineIsCompiling path is
reflecting CompilationPipeline.isCompiling as a public property, but that member
isn’t publicly available on the newer Unity range so the delegate never binds.
Update CreatePipelineIsCompilingDelegate to use a supported signal instead, such
as the compilation started/finished events, or only fall back to a non-public
lookup when the member exists on the target Unity versions. Keep the change
localized to EditorStateCache.GetActualIsCompiling and its helper delegate
setup.

In `@MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs`:
- Around line 164-187: Keep service-race failures inside the retry loop: in
OnAfterAssemblyReloadCore, move the early transport/state checks that can throw
(such as UseHttpTransport and TransportManager.IsRunning) into the existing
try/catch around TransportManager.StartAsync, or wrap the whole resume attempt
block so any transient service lookup/race exception is caught and triggers the
retry scheduling path. Make sure SessionState.EraseBool(ResumeSessionKey) only
happens on confirmed success or intentional abort, so a thrown pre-start check
doesn’t cancel the fire-and-forget resume task without rescheduling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f277cbf6-2f05-45fe-89e0-1ebe64b90915

📥 Commits

Reviewing files that changed from the base of the PR and between d87ca19 and 3015117.

📒 Files selected for processing (17)
  • MCPForUnity/Editor/Constants/EditorPrefKeys.cs
  • MCPForUnity/Editor/Services/EditorStateCache.cs
  • MCPForUnity/Editor/Services/HttpAutoStartHandler.cs
  • MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs
  • MCPForUnity/Editor/Services/IServerManagementService.cs
  • MCPForUnity/Editor/Services/ServerManagementService.cs
  • MCPForUnity/Editor/Services/Transport/TransportManager.cs
  • MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
  • MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/HttpAutoStartHandlerTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/HttpAutoStartHandlerTests.cs.meta
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/HttpBridgeReloadHandlerTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/HttpBridgeReloadHandlerTests.cs.meta
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/TransportManagerTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Services/TransportManagerTests.cs.meta
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/TestUtilities.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/Characterization/Windows_Characterization.cs
💤 Files with no reviewable changes (2)
  • MCPForUnity/Editor/Constants/EditorPrefKeys.cs
  • MCPForUnity/Editor/Windows/EditorPrefs/EditorPrefsWindow.cs

Comment on lines +53 to +61
Task<bool> inFlight = mode == TransportMode.Http ? _httpStartTask : _stdioStartTask;
if (inFlight != null && !inFlight.IsCompleted)
{
return inFlight;
}

Task<bool> started = StartCoreAsync(mode);
if (mode == TransportMode.Http) _httpStartTask = started;
else _stdioStartTask = started;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate mode before reading or writing the coalescing slot.

Line 53 treats any non-Http value as Stdio; if an invalid enum reaches StartAsync while a Stdio start is pending, it returns that unrelated task instead of preserving the unsupported-mode contract. Use a switch for both lookup and assignment.

Suggested fix
-            Task<bool> inFlight = mode == TransportMode.Http ? _httpStartTask : _stdioStartTask;
+            Task<bool> inFlight;
+            switch (mode)
+            {
+                case TransportMode.Http:
+                    inFlight = _httpStartTask;
+                    break;
+                case TransportMode.Stdio:
+                    inFlight = _stdioStartTask;
+                    break;
+                default:
+                    return Task.FromException<bool>(
+                        new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported transport mode"));
+            }
             if (inFlight != null && !inFlight.IsCompleted)
             {
                 return inFlight;
             }
 
             Task<bool> started = StartCoreAsync(mode);
-            if (mode == TransportMode.Http) _httpStartTask = started;
-            else _stdioStartTask = started;
+            switch (mode)
+            {
+                case TransportMode.Http:
+                    _httpStartTask = started;
+                    break;
+                case TransportMode.Stdio:
+                    _stdioStartTask = started;
+                    break;
+            }
             return started;
📝 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
Task<bool> inFlight = mode == TransportMode.Http ? _httpStartTask : _stdioStartTask;
if (inFlight != null && !inFlight.IsCompleted)
{
return inFlight;
}
Task<bool> started = StartCoreAsync(mode);
if (mode == TransportMode.Http) _httpStartTask = started;
else _stdioStartTask = started;
Task<bool> inFlight;
switch (mode)
{
case TransportMode.Http:
inFlight = _httpStartTask;
break;
case TransportMode.Stdio:
inFlight = _stdioStartTask;
break;
default:
return Task.FromException<bool>(
new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported transport mode"));
}
if (inFlight != null && !inFlight.IsCompleted)
{
return inFlight;
}
Task<bool> started = StartCoreAsync(mode);
switch (mode)
{
case TransportMode.Http:
_httpStartTask = started;
break;
case TransportMode.Stdio:
_stdioStartTask = started;
break;
}
return started;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MCPForUnity/Editor/Services/Transport/TransportManager.cs` around lines 53 -
61, Validate the transport mode before touching the coalescing tasks in
`TransportManager.StartAsync`; the current ternary treats every non-`Http` value
as `Stdio`, which can incorrectly reuse `_stdioStartTask` for unsupported enum
values. Refactor the lookup and assignment logic into a `switch` on `mode` so
only `TransportMode.Http` and `TransportMode.Stdio` map to `_httpStartTask` and
`_stdioStartTask`, and let invalid modes preserve the unsupported-mode contract
instead of returning an unrelated task.

… mode + retry-loop races

Review follow-ups:
- CompilationPipeline.isCompiling does not exist on the supported Unity range
  (reflection probe on 2021.3 and 6000.4: neither public nor non-public), so
  the reflected play-mode double-check never resolved and GetActualIsCompiling
  silently fell back to the raw signal — the CoplayDev#549 false positive was never
  actually mitigated. Track compilationStarted/compilationFinished events
  instead (public across the range); in Play mode trust the event-tracked
  state, outside it the raw signal is reliable.
- TransportManager.StartAsync validates the mode before touching the
  coalescing slots, matching the class's unsupported-mode contract instead of
  routing unknown values to the stdio slot.
- The resume retry loop's transport/IsRunning pre-checks moved inside the
  attempt try: a service read racing the reload boundary now burns a retry
  instead of killing the fire-and-forget task with the flag still set.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working safe-to-test Triggers CI checks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] v10.0.0: HTTP auto-start and reload-resume both lost to delayCall/domain-reload race (Windows)

1 participant