Skip to content

fix: reliable auto-start and multi-instance connection support#1121

Open
emiapwil wants to merge 32 commits into
CoplayDev:mainfrom
emiapwil:main
Open

fix: reliable auto-start and multi-instance connection support#1121
emiapwil wants to merge 32 commits into
CoplayDev:mainfrom
emiapwil:main

Conversation

@emiapwil
Copy link
Copy Markdown

Summary

Fixes two fundamental reliability issues: (1) Unity MCP auto-start never fires on fresh editor launch because EditorPrefs is not initialized during [InitializeOnLoad], and (2) port sharing on Linux due to SO_REUSEADDR being the default, plus multi-instance discovery support.

Changes

HttpAutoStartHandler.cs

  • Root cause: EditorPrefs returns default values during [InitializeOnLoad]. The static constructor reads AutoStartOnLoad=False and returns before scheduling EditorApplication.delayCall. The delayCall never fires, so the bridge never auto-starts.
  • Fix: Use EditorApplication.update with retry (up to 120 frames), polling EditorPrefs.GetBool each frame until it returns the correct persisted value.

StdioBridgeHost.cs

  • Root cause: ExclusiveAddressUse was gated behind #if UNITY_EDITOR_OSX. On Linux, TcpListener defaults to SO_REUSEADDR, allowing multiple processes to bind the same TCP port. A new Unity instance silently shares port 6400 with an existing one.
  • Fix: Enable ExclusiveAddressUse = true unconditionally on all platforms.
  • Multi-instance support: Added pid field to heartbeat. Instance-scoped status file alongside project-scoped file.
  • Stop cleanup: Deletes both project-scoped and instance-scoped status files.

PortManager.cs

  • ExclusiveAddressUse: Unconditional fix in IsPortAvailable().
  • GetPortWithFallback: Validates stored port is available before returning it.
  • PortConfig: Added pid field. Instance-scoped port file for multi-instance tracking.

actions-user and others added 30 commits April 27, 2026 01:31
…o-beta-24972307282

chore: sync main (v9.6.8) into beta
Use ScreenCapture.CaptureScreenshotAsTexture() for game_view screenshots
when include_image=true and in Play mode. This captures the final composited
frame including UI Toolkit overlays, which camera.Render() misses since
UI Toolkit renders at the compositor level after camera rendering.

The camera-based path is still used when a specific camera is requested
or when not in Play mode.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <[email protected]>
Co-Authored-By: Happy <[email protected]>
PlayMode tests require entering play mode which triggers a domain reload.
On large projects this can take >15s, causing the hardcoded 15s init
timeout to auto-fail the test job before tests actually start.

This adds an `init_timeout` parameter to `run_tests` that flows through
the Python server → C# RunTests handler → TestJobManager. When set, the
per-job timeout overrides the 15s default. The value is persisted across
domain reloads via SessionState.

Changes:
- Python: Add `init_timeout` param to `run_tests()` function signature
- C# RunTests: Read `initTimeout` param and pass to `StartJob()`
- C# TestJobManager: Per-job `InitTimeoutMs` field with fallback to
  `DefaultInitializationTimeoutMs` (15s), persisted in SessionState

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Clamp initTimeoutMs in StartJob: negative values → 0, cap at 600s
- Python: reject init_timeout <= 0 with explicit error before calling Unity
- Add 3 C# EditMode tests for per-job InitTimeoutMs behavior
  (custom timeout, default timeout auto-fail, persist/restore)
- Add 4 Python tests for init_timeout forwarding and validation

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Remove unused _originalJobs field
- Add Assume.That guards for EditorApplication.isCompiling/isUpdating
  so the test is skipped (inconclusive) rather than producing misleading
  results when the editor is mid-compilation

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The persist/restore test writes synthetic jobs to SessionState, which
survives domain reloads and would be re-hydrated by TestJobManager's
[InitializeOnLoadMethod] hook. Flush the cleaned in-memory state back
to SessionState in TearDown so test artifacts don't leak across runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…it-timeout

feat: Add configurable init_timeout for PlayMode test initialization
…eta.2-25116604754

chore: update Unity package to beta version 9.6.9-beta.2
…eta.3-25133350008

chore: update Unity package to beta version 9.6.9-beta.3
1.Add Compat based scripts revolving around UnityCompatShims.cs, that will document our current API Compatibility changes in several files.
2.Add custom screenshot folder selection
…eta.4-25294688169

chore: update Unity package to beta version 9.6.9-beta.4
…uitoolkit-screenshot-capture

fix: include UI Toolkit overlays in game_view screenshots with include_image
…eta.5-25294857075

chore: update Unity package to beta version 9.6.9-beta.5
…v#1103)

* fix: align CaptureComposited with renamed Project*-folder API

PR CoplayDev#1040 added CaptureComposited referencing the old Assets-folder names
(CaptureFromCameraToAssetsFolder, AssetsRelativePath) and called
PrepareCaptureResult without the now-required folderOverride argument.
Renames into the Project* equivalents; same fix at the call sites in
ManageScene.cs.

Closes CoplayDev#1100

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* ci: gate releases on test success and trigger tests on PRs

Beta-release was publishing to PyPI in parallel with Unity Tests, so
broken commits could ship if Unity tests failed (as happened with CoplayDev#1100
on 9.6.9-beta.5). Make publish/version-bump jobs depend on the test
jobs in both beta-release.yml and release.yml. The whole release halts
before any irreversible commit/tag/push if either test job fails.

Also extends the test workflows to fire on PRs so failures are caught
before merge:
- python-tests: pull_request trigger; runs on every PR (no secrets).
- unity-tests: pull_request_target [labeled] trigger gated on the
  safe-to-test label and on the PR being from a fork. Maintainers
  apply the label after reviewing the diff; the workflow then runs
  with UNITY_LICENSE in scope against the PR head SHA. Re-pushed
  commits do NOT auto-trigger; maintainer must remove and re-apply
  the label to re-run after additional review.

In-repo PRs continue to be tested via the existing push trigger, so
no labeling friction for collaborator branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(screenshot): propagate folderOverride to composited and specific-camera paths

Two pre-existing inconsistencies surfaced by CodeRabbit on CoplayDev#1103:

1. CaptureComposited dropped the caller's output_folder by hardcoding
   folderOverride: null in PrepareCaptureResult and the camera fallbacks.
   Adds the parameter to CaptureComposited's signature and plumbs it
   through both fallback paths.

2. The targetCamera and includeImage-in-play paths in ManageScene's
   game_view screenshot did not resolve cmd.outputFolder, so a request
   that selected a specific camera would always write to the default
   folder. Resolve via ScreenshotPreferences.Resolve as the other paths
   already do, and gate AssetDatabase.ImportAsset on IsUnderAssets so
   non-Assets folders don't trigger a futile import.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* ci(unity-tests): harden pull_request_target and gate artifact upload

Address CodeRabbit security review on CoplayDev#1103:

- persist-credentials: false on the checkout step, so GITHUB_TOKEN is
  not written to disk and cannot be read by subsequent steps running
  PR-controlled code.
- Explicit permissions: contents: read on the testAllModes job, scoping
  the workflow's token down from the default read/write set.
- Skip upload-artifact when the main test step was skipped (e.g.,
  because the preceding domain-reload step failed without
  continue-on-error). Avoids the noisy "No files were found" error
  on top of an already-failed run.

The label-gated trigger plus these mitigations narrow the blast radius
of the pull_request_target + checkout-PR-head pattern. The remaining
trust boundary is the maintainer review before applying safe-to-test;
documenting the review checklist (especially TestProjects/UnityMCPTests
diffs) is a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
…eta.6-25339361610

chore: update Unity package to beta version 9.6.9-beta.6
…ayDev#1106)

The (Type, bool includeInactive) overload guarded its modern-API
branch with #elif UNITY_2023_1_OR_NEWER while the legacy reflection
helper LegacyFindObjectsOfType is gated by #if !UNITY_2022_3_OR_NEWER.
That left the entire Unity 2022.3.x band falling through to a
legacy helper that the preprocessor had already excluded, producing
CS0103: 'LegacyFindObjectsOfType' does not exist in the current
context (e.g. on 2022.3.62f2, see CoplayDev#1105).

The 3-arg FindObjectsByType(Type, FindObjectsInactive, FindObjectsSortMode)
overload has been available since Unity 2022.2, so 2022.3 can use the
modern API directly. Aligning the threshold with the rest of the file
(2022_3_OR_NEWER) closes the gap with no API loss.

Closes CoplayDev#1105

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
…layDev#1108)

Both the push and pull_request_target paths filters listed
MCPForUnity/Editor/** but not Runtime/, so a PR (or push) that
modified only files under MCPForUnity/Runtime/** would not trigger
Unity tests at all -- including the safe-to-test label flow on a
fork PR. CodeRabbit flagged this as a Low/💤 nitpick on CoplayDev#1103;
PR CoplayDev#1106 made it a concrete recurrence: applying safe-to-test had
no effect because the Runtime-only diff was filtered out.

Adding Runtime/** mirrors the asmdef layout (Editor and Runtime
are the two assembly roots whose code can break Unity compilation),
matches the codebase's "domain symmetry" convention, and closes the
silent-skip path the label gate was designed to prevent.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
…eta.7-25350517238

chore: update Unity package to beta version 9.6.9-beta.7
emiapwil added 2 commits May 8, 2026 22:17
…connections

- Remove #if UNITY_EDITOR_OSX guard from ExclusiveAddressUse in CreateConfiguredListener and IsPortAvailable
- Add process ID to status heartbeat payload for multi-instance discovery
- Write instance-specific status file (hash-pid.json) alongside project file
- GetPortWithFallback now validates stored port availability before returning
- Stop() cleans up both project and instance-specific status files
- PortManager.SavePort includes PID and writes instance-scoped file
- HttpAutoStartHandler: use EditorApplication.update instead of delayCall
  to poll until EditorPrefs is initialized, fixing AutoStartOnLoad not being
  read during early editor startup
- StdioBridgeHost: set ExclusiveAddressUse on all platforms (was macOS-only)
  to prevent SO_REUSEADDR port sharing on Linux; add PID to status heartbeat;
  write instance-scoped status file for multi-instance discovery
- PortManager: set ExclusiveAddressUse on all platforms; GetPortWithFallback
  validates stored port availability before returning; add PID to PortConfig;
  write instance-scoped port files
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

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: 08bcb00b-25d4-49ec-8cd9-3f7d0346bfed

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

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.

5 participants