Skip to content

fix: unblock beta compile and gate releases on test success#1103

Merged
dsarno merged 4 commits intoCoplayDev:betafrom
dsarno:dlight/hardcore-heyrovsky-ef06ac
May 4, 2026
Merged

fix: unblock beta compile and gate releases on test success#1103
dsarno merged 4 commits intoCoplayDev:betafrom
dsarno:dlight/hardcore-heyrovsky-ef06ac

Conversation

@dsarno
Copy link
Copy Markdown
Collaborator

@dsarno dsarno commented May 4, 2026

Summary

Two related changes responding to the broken 9.6.9-beta.5 publish (issue #1100):

  • fix: align CaptureComposited with the renamed Project*-folder API. PR fix: include UI Toolkit overlays in game_view screenshots with include_image #1040 added CaptureComposited referencing the old CaptureFromCameraToAssetsFolder / AssetsRelativePath names and called PrepareCaptureResult without the now-required folderOverride arg. Renames into the Project* equivalents in ScreenshotUtility.cs, plus the matching call sites in ManageScene.cs. Closes Unity 2022.3 compile errors on beta 9.6.9-beta.5 / commit 37ef016 #1100.
  • ci: gate releases on test success and run tests on PRs.
    • beta-release.yml and release.yml publish/bump jobs now needs: [unity_tests, python_tests] via workflow_call. The whole release halts before any irreversible commit/tag/push if either fails. Previously PyPI shipped 28s after push, before Unity Tests had even compiled the project.
    • python-tests.yml now has a pull_request trigger; runs on every PR (no secrets).
    • unity-tests.yml now has pull_request_target: types: [labeled] gated on a safe-to-test label and the PR being from a fork. Maintainers review the diff, apply the label, and unity-tests runs with UNITY_LICENSE against the PR's 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.

Maintainer setup

After this lands, create a safe-to-test label in the repo (Issues/PRs → Labels → New). That's the entire one-time setup; the rest is automatic.

Test plan

  • Verify Unity recompiles cleanly after the rename (test project compiles without the four CS errors from Unity 2022.3 compile errors on beta 9.6.9-beta.5 / commit 37ef016 #1100).
  • Push a no-op commit to a beta-targeted branch and confirm unity-tests and python-tests jobs both run before any release/publish job starts.
  • Open a fork PR (or simulate one) without the label — confirm unity-tests does NOT run.
  • Apply safe-to-test to a fork PR — confirm unity-tests runs against the PR's head SHA.
  • Push a new commit to the labeled fork PR — confirm unity-tests does NOT auto-rerun.
  • Manually dispatch release.yml on main and confirm it runs unity_tests + python_tests against beta before bump runs.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Screenshot capture now returns project-relative paths for more consistent image import and reporting.
  • Chores

    • CI/CD updated with dedicated Unity and Python test gates that must pass before version bumps or beta publishes.
    • Test workflows now support reusable invocation and expanded triggers, and checkout logic respects invoked refs.

dsarno and others added 2 commits May 4, 2026 12:11
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

Adds reusable test gates and wiring so Unity/Python tests run as prerequisites for beta/release jobs, and changes screenshot capture fallbacks to produce project-relative paths (ProjectRelativePath) instead of assets-relative paths in editor and runtime flows.

Changes

CI/CD Workflow Gating

Layer / File(s) Summary
Reusable Workflow Interfaces
.github/workflows/python-tests.yml, .github/workflows/unity-tests.yml
Both workflows now support workflow_call with an optional ref input and make actions/checkout use the caller-provided ref (with fallbacks) for deterministic reusable invocation.
Gate Jobs Added
.github/workflows/release.yml, .github/workflows/beta-release.yml
New unity_tests and python_tests gate jobs added that call the reusable workflows (passing ref: beta from release/beta pipelines); unity_tests inherits secrets.
Job Dependencies / Wiring
.github/workflows/release.yml, .github/workflows/beta-release.yml
Release/beta jobs that create bumps, PRs, or publish (e.g., bump, update_unity_beta_version, publish_pypi_prerelease) now include needs: [unity_tests, python_tests], requiring both gates to complete first.
Trigger & PR Safety
.github/workflows/unity-tests.yml, .github/workflows/python-tests.yml
unity-tests.yml narrows PR handling to pull_request_target with types: [labeled] gated by safe-to-test; python-tests.yml now triggers on pull_request for main/beta and supports workflow_call ref input.
Test Execution Changes
.github/workflows/unity-tests.yml
Unity test job runs domain-reload-specific tests first when license secrets exist, then the main suite with continue-on-error: true; test result XML is parsed and the job fails if any tests report failures; artifact upload skipped when tests were skipped.

Screenshot Capture Path Handling

Layer / File(s) Summary
API / Signature
MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs
CaptureComposited gains an optional folderOverride parameter and forwards it through capture preparation and camera fallbacks.
Fallback Behavior
MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs
When ScreenCapture is unavailable or returns null, fallbacks now call CaptureFromCameraToProjectFolder(...) (project-relative) and forward folderOverride rather than using asset-folder fallbacks.
Return Path Field
MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs
When includeImage is used, returned ScreenshotCaptureResult now uses ProjectRelativePath instead of an assets-relative path.
Editor Integration & Responses
MCPForUnity/Editor/Tools/ManageScene.cs
Editor capture and composited-play-mode branches now call project-folder capture helpers, import assets using result.ProjectRelativePath, and set the response payload path to result.ProjectRelativePath (BuildScreenshotResponseData updated accordingly).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • CoplayDev/unity-mcp#220: Introduced making .github/workflows/unity-tests.yml reusable (workflow_call, ref input, checkout ref logic) which this PR builds on.
  • CoplayDev/unity-mcp#628: Modifies beta-release.yml (TestPyPI beta publish / beta pipeline) and is related to gating/publishing changes here.
  • CoplayDev/unity-mcp#1040: Touches the same screenshot capture codepaths (CaptureComposited / ManageScene) and is directly related to path/result-field changes.

Poem

🐰 I hopped through CI with nimble feet,

Gates of tests I dared to meet.
Screenshots now live in project land,
Paths consistent — tidy and planned.
A carrot for builds, a clap of paws — hooray!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: fixing a compile issue and gating releases on test success, which aligns with the PR's primary objectives.
Description check ✅ Passed The PR description is comprehensive and covers all required template sections: Summary (with fix and CI changes clearly explained), Type of Change (implicit: bug fix + CI), Test plan with specific checkboxes, and Related Issues (closes #1100).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ 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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov-commenter
Copy link
Copy Markdown

Welcome to Codecov 🎉

Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests.

ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment

Thanks for integrating Codecov - We've got you covered ☂️

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (4)
MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs (1)

251-255: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the caller’s output folder in CaptureComposited.

This branch now hardcodes folderOverride: null, so composited captures always land in Assets/Screenshots and ignore any caller-selected output_folder. The two camera fallbacks have the same problem because they also have no way to receive the override.

Suggested fix
         public static ScreenshotCaptureResult CaptureComposited(
             string fileName = null,
             int superSize = 1,
             bool ensureUniqueFileName = true,
             bool includeImage = false,
-            int maxResolution = 0)
+            int maxResolution = 0,
+            string folderOverride = null)
         {
             if (!IsScreenCaptureModuleAvailable)
             {
                 var fallbackCamera = FindAvailableCamera();
                 if (fallbackCamera != null)
-                    return CaptureFromCameraToProjectFolder(fallbackCamera, fileName, superSize, ensureUniqueFileName, includeImage, maxResolution);
+                    return CaptureFromCameraToProjectFolder(
+                        fallbackCamera, fileName, superSize, ensureUniqueFileName,
+                        includeImage, maxResolution, folderOverride: folderOverride);

                 throw new InvalidOperationException("ScreenCapture module is unavailable and no fallback camera found.");
             }

-            ScreenshotCaptureResult result = PrepareCaptureResult(fileName, superSize, ensureUniqueFileName, folderOverride: null, isAsync: false);
+            ScreenshotCaptureResult result = PrepareCaptureResult(
+                fileName, superSize, ensureUniqueFileName,
+                folderOverride: folderOverride, isAsync: false);
@@
                 if (tex == null)
                 {
                     // Fallback to camera-based if ScreenCapture fails
                     var cam = FindAvailableCamera();
                     if (cam != null)
-                        return CaptureFromCameraToProjectFolder(cam, fileName, superSize, ensureUniqueFileName, includeImage, maxResolution);
+                        return CaptureFromCameraToProjectFolder(
+                            cam, fileName, superSize, ensureUniqueFileName,
+                            includeImage, maxResolution, folderOverride: folderOverride);
                     throw new InvalidOperationException("ScreenCapture.CaptureScreenshotAsTexture returned null and no fallback camera available.");
                 }

Also applies to: 260-260, 271-274

🤖 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/Runtime/Helpers/ScreenshotUtility.cs` around lines 251 - 255, The
composited capture and camera fallback branches ignore the caller's output
folder because they call CaptureComposited and CaptureFromCameraToProjectFolder
with folderOverride: null (hardcoding Assets/Screenshots); update the calls in
CaptureComposited fallback branches and the two camera fallback sites to accept
and forward the caller's output folder parameter (e.g., pass the existing
output_folder or a new folderOverride variable through to CaptureComposited and
CaptureFromCameraToProjectFolder instead of null) so captures respect the
caller-selected output folder; locate and change calls in CaptureComposited,
FindAvailableCamera fallback branches, and the two camera fallback calls to
propagate the folder argument.
MCPForUnity/Editor/Tools/ManageScene.cs (1)

597-607: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Specific-camera screenshots still ignore output_folder.

When camera is supplied, this branch bypasses cmd.outputFolder and always writes to the default screenshot folder, unlike the non-play/default branches. That makes the same API request behave differently depending on camera selection.

Suggested fix
                 bool includeImage = cmd.includeImage ?? false;
                 int maxResolution = cmd.maxResolution ?? 0; // 0 = let ScreenshotUtility default to 640
                 string cameraRef = cmd.camera;
+                string folderOverride = ScreenshotPreferences.Resolve(cmd.outputFolder);
                 string captureSource = string.IsNullOrWhiteSpace(cmd.captureSource)
                     ? "game_view"
                     : cmd.captureSource.Trim().ToLowerInvariant();
@@
                     ScreenshotCaptureResult result = ScreenshotUtility.CaptureFromCameraToProjectFolder(
                         targetCamera, fileName, resolvedSuperSize, ensureUniqueFileName: true,
-                        includeImage: includeImage, maxResolution: maxResolution);
+                        includeImage: includeImage, maxResolution: maxResolution,
+                        folderOverride: folderOverride);

-                    AssetDatabase.ImportAsset(result.ProjectRelativePath, ImportAssetOptions.ForceSynchronousImport);
+                    if (ScreenshotUtility.IsUnderAssets(result.ProjectRelativePath))
+                        AssetDatabase.ImportAsset(result.ProjectRelativePath, ImportAssetOptions.ForceSynchronousImport);
                     string message = $"Screenshot captured to '{result.ProjectRelativePath}' (camera: {targetCamera.name}).";
🤖 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/Tools/ManageScene.cs` around lines 597 - 607, The camera
branch always calls ScreenshotUtility.CaptureFromCameraToProjectFolder (with no
output folder) so screenshots for a supplied targetCamera ignore
cmd.outputFolder; change the branch in ManageScene (the targetCamera != null
block) to pass the intended output folder into the capture routine (either call
a Capture... overload that takes an outputFolder or build the correct
destination path from cmd.outputFolder and fileName and call the capture method
that writes to that path), then use the returned result.ProjectRelativePath for
AssetDatabase.ImportAsset and for the SuccessResponse as before (ensure the
symbol names: targetCamera, ScreenshotUtility.CaptureFromCameraToProjectFolder /
alternative overload, result.ProjectRelativePath, BuildScreenshotResponseData).
.github/workflows/unity-tests.yml (2)

22-52: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

pull_request_target + checkout of PR head SHA + exposed secrets is the canonical "pwn request" pattern.

"Combining pull_request_target workflow trigger with an explicit checkout of an untrusted PR is a dangerous practice that may lead to repository compromise." When a workflow is triggered by pull_request_target, the GITHUB_TOKEN is granted read/write repository permission even from a public fork.

The specific risks here:

  1. Line 52 explicitly checks out github.event.pull_request.head.sha (untrusted fork code) while running in the base-branch context with secrets.
  2. Lines 84–111: game-ci/unity-test-runner receives UNITY_EMAIL, UNITY_PASSWORD, UNITY_SERIAL, and UNITY_LICENSE as environment variables. Unity C# test scripts have full access to System.Environment.GetEnvironmentVariable() and unrestricted networking, enabling secret exfiltration from within test code.
  3. Line 49 (actions/checkout@v4) does not set persist-credentials: false, meaning the GITHUB_TOKEN is persisted to disk by default, and any subsequent step's running code can simply read it.
  4. Setting "require approval for all workflow runs from public fork" won't help with the pull_request_target trigger since workflows triggered by these events will always run, regardless of approval settings.

The safe-to-test label gate (lines 34–37) is the sole mitigation and relies entirely on the reviewing maintainer catching malicious test code before applying the label.

Recommended hardening:

  • Add persist-credentials: false to the checkout step to prevent GITHUB_TOKEN disk persistence.
  • Add an explicit permissions: block scoping down to the minimum needed (e.g., contents: read).
  • Consider documenting a mandatory diff-review checklist (specifically: any changes under TestProjects/UnityMCPTests/) as a prerequisite before applying safe-to-test.
  • Evaluate whether UNITY_SERIAL and UNITY_PASSWORD are both required; UNITY_LICENSE (activation XML) alone may suffice for game-ci's headless activation, reducing the credential blast radius.
🛡️ Minimal hardening diff
  steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      with:
        lfs: true
        ref: ${{ inputs.ref || github.event.pull_request.head.sha || github.ref }}
+       persist-credentials: false

Add a permissions block to the job:

  testAllModes:
    name: Test in ${{ matrix.testMode }}
    runs-on: ubuntu-latest
+   permissions:
+     contents: read
    if: >
🤖 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 @.github/workflows/unity-tests.yml around lines 22 - 52, The workflow uses
pull_request_target with an explicit checkout of the PR head and exposes secrets
to test runners; update the testAllModes job to harden it by (1) adding a
permissions block (e.g., permissions: contents: read) at job level to limit
GITHUB_TOKEN scope, (2) set persist-credentials: false on the
actions/checkout@v4 step to avoid persisting GITHUB_TOKEN to disk, (3) stop
checking out untrusted PR HEAD SHA by removing the explicit
github.event.pull_request.head.sha ref use in the checkout input (only checkout
the base repo by default and only fetch PR head after the safe-to-test label is
verified), and (4) remove/limit passing UNITY_EMAIL, UNITY_PASSWORD,
UNITY_SERIAL secrets into the game-ci/unity-test-runner step (pass only
UNITY_LICENSE if sufficient) and gate any secret usage behind the safe-to-test
label check; reference the pull_request_target trigger, testAllModes job,
actions/checkout@v4, safe-to-test label, and
UNITY_EMAIL/UNITY_PASSWORD/UNITY_SERIAL/UNITY_LICENSE in your changes.

132-136: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Artifact upload may error with an empty path when Run domain reload tests fails.

Run domain reload tests (line 84) has no continue-on-error: true. If it fails, the Run tests step (line 98) is skipped due to the implicit success() condition, leaving steps.tests.outputs.artifactsPath unset. The upload step runs (because of always()) but actions/upload-artifact@v4 with an empty path will error with "No files were found", adding noise on top of an already-failed run.

🛠️ Proposed fix
  - uses: actions/upload-artifact@v4
-   if: always() && steps.detect.outputs.unity_ok == 'true'
+   if: always() && steps.detect.outputs.unity_ok == 'true' && steps.tests.outcome != 'skipped'
    with:
      name: Test results for ${{ matrix.testMode }}
      path: ${{ steps.tests.outputs.artifactsPath }}
🤖 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 @.github/workflows/unity-tests.yml around lines 132 - 136, The
upload-artifact step errors when steps.tests.outputs.artifactsPath is unset
because the "Run domain reload tests" step can fail and skip the "Run tests"
step; fix by either making "Run domain reload tests" continue-on-error: true or
by guarding the upload step's if condition to check the artifactsPath is
non-empty (e.g. require steps.tests.outputs.artifactsPath != '' &&
steps.detect.outputs.unity_ok == 'true') so actions/upload-artifact@v4 only runs
when the tests step produced artifacts.
🧹 Nitpick comments (1)
.github/workflows/unity-tests.yml (1)

22-28: 💤 Low value

Verify that paths + types: [labeled] combination on pull_request_target behaves as expected.

If you define both branches/branches-ignore and paths/paths-ignore, the workflow will only run when both filters are satisfied. For types: [labeled], this means the labeled event only triggers the workflow if the PR also has changed files matching the paths filter. A fork PR that changes only Python/Server files but has the safe-to-test label added would silently not trigger Unity tests — maintainers might not realize the tests didn't run. Confirm this is the desired gating behavior, and consider adding a comment or docs note to warn maintainers to verify tests actually ran after labeling.

🤖 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 @.github/workflows/unity-tests.yml around lines 22 - 28, The
pull_request_target event block uses types: [labeled] together with branches and
paths filters, which causes the workflow to only run on labeled events when the
PR also changes files matching the paths filter; update the workflow to avoid
silently skipping tests by either removing the paths filter for the labeled
trigger, adding a separate pull_request_target entry (or workflow) that listens
to types: [labeled] without paths/branches filtering, or add an inline comment
and documentation warning maintainers that labeled events require matching paths
to run and to verify tests ran; locate the pull_request_target block and adjust
the filters or add a second labeled-only trigger so the safe-to-test label
reliably kicks off Unity tests.
🤖 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.

Outside diff comments:
In @.github/workflows/unity-tests.yml:
- Around line 22-52: The workflow uses pull_request_target with an explicit
checkout of the PR head and exposes secrets to test runners; update the
testAllModes job to harden it by (1) adding a permissions block (e.g.,
permissions: contents: read) at job level to limit GITHUB_TOKEN scope, (2) set
persist-credentials: false on the actions/checkout@v4 step to avoid persisting
GITHUB_TOKEN to disk, (3) stop checking out untrusted PR HEAD SHA by removing
the explicit github.event.pull_request.head.sha ref use in the checkout input
(only checkout the base repo by default and only fetch PR head after the
safe-to-test label is verified), and (4) remove/limit passing UNITY_EMAIL,
UNITY_PASSWORD, UNITY_SERIAL secrets into the game-ci/unity-test-runner step
(pass only UNITY_LICENSE if sufficient) and gate any secret usage behind the
safe-to-test label check; reference the pull_request_target trigger,
testAllModes job, actions/checkout@v4, safe-to-test label, and
UNITY_EMAIL/UNITY_PASSWORD/UNITY_SERIAL/UNITY_LICENSE in your changes.
- Around line 132-136: The upload-artifact step errors when
steps.tests.outputs.artifactsPath is unset because the "Run domain reload tests"
step can fail and skip the "Run tests" step; fix by either making "Run domain
reload tests" continue-on-error: true or by guarding the upload step's if
condition to check the artifactsPath is non-empty (e.g. require
steps.tests.outputs.artifactsPath != '' && steps.detect.outputs.unity_ok ==
'true') so actions/upload-artifact@v4 only runs when the tests step produced
artifacts.

In `@MCPForUnity/Editor/Tools/ManageScene.cs`:
- Around line 597-607: The camera branch always calls
ScreenshotUtility.CaptureFromCameraToProjectFolder (with no output folder) so
screenshots for a supplied targetCamera ignore cmd.outputFolder; change the
branch in ManageScene (the targetCamera != null block) to pass the intended
output folder into the capture routine (either call a Capture... overload that
takes an outputFolder or build the correct destination path from
cmd.outputFolder and fileName and call the capture method that writes to that
path), then use the returned result.ProjectRelativePath for
AssetDatabase.ImportAsset and for the SuccessResponse as before (ensure the
symbol names: targetCamera, ScreenshotUtility.CaptureFromCameraToProjectFolder /
alternative overload, result.ProjectRelativePath, BuildScreenshotResponseData).

In `@MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs`:
- Around line 251-255: The composited capture and camera fallback branches
ignore the caller's output folder because they call CaptureComposited and
CaptureFromCameraToProjectFolder with folderOverride: null (hardcoding
Assets/Screenshots); update the calls in CaptureComposited fallback branches and
the two camera fallback sites to accept and forward the caller's output folder
parameter (e.g., pass the existing output_folder or a new folderOverride
variable through to CaptureComposited and CaptureFromCameraToProjectFolder
instead of null) so captures respect the caller-selected output folder; locate
and change calls in CaptureComposited, FindAvailableCamera fallback branches,
and the two camera fallback calls to propagate the folder argument.

---

Nitpick comments:
In @.github/workflows/unity-tests.yml:
- Around line 22-28: The pull_request_target event block uses types: [labeled]
together with branches and paths filters, which causes the workflow to only run
on labeled events when the PR also changes files matching the paths filter;
update the workflow to avoid silently skipping tests by either removing the
paths filter for the labeled trigger, adding a separate pull_request_target
entry (or workflow) that listens to types: [labeled] without paths/branches
filtering, or add an inline comment and documentation warning maintainers that
labeled events require matching paths to run and to verify tests ran; locate the
pull_request_target block and adjust the filters or add a second labeled-only
trigger so the safe-to-test label reliably kicks off Unity tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4846f9ce-e60f-4e82-a726-6cb445c54ac1

📥 Commits

Reviewing files that changed from the base of the PR and between 37ef016 and 642687f.

📒 Files selected for processing (6)
  • .github/workflows/beta-release.yml
  • .github/workflows/python-tests.yml
  • .github/workflows/release.yml
  • .github/workflows/unity-tests.yml
  • MCPForUnity/Editor/Tools/ManageScene.cs
  • MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs

dsarno and others added 2 commits May 4, 2026 12:26
…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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
.github/workflows/unity-tests.yml (2)

119-119: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Quote the artifactsPath expansion in the find command

An unquoted ${{ steps.tests.outputs.artifactsPath }} will cause find to misparse the path if it ever contains spaces.

🛡️ Proposed fix
- RESULTS_XML=$(find ${{ steps.tests.outputs.artifactsPath }} -name '*.xml' 2>/dev/null | head -1)
+ RESULTS_XML=$(find "${{ steps.tests.outputs.artifactsPath }}" -name '*.xml' 2>/dev/null | head -1)
🤖 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 @.github/workflows/unity-tests.yml at line 119, The find command that sets
RESULTS_XML uses an unquoted expansion of ${{ steps.tests.outputs.artifactsPath
}}, which will break if the path contains spaces; update the command that
assigns RESULTS_XML (the find invocation) to quote the expansion so find sees
the full path as a single argument (i.e., use a quoted "${{
steps.tests.outputs.artifactsPath }}" in the find command and preserve the rest
of the flags/redirections).

124-133: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Empty FAILED produces a false-positive failure

If grep finds no failed="…" attribute (e.g., format variation, NUnit2 failures= vs NUnit3 failed=), FAILED is an empty string. [ "$FAILED" != "0" ] then evaluates "" != "0" as true, causing the job to exit 1 with a misleading " test(s) failed" message even when all tests passed.

🛡️ Proposed fix
  FAILED=$(grep -oP 'failed="\K[0-9]+' "$RESULTS_XML" | head -1)
  PASSED=$(grep -oP 'passed="\K[0-9]+' "$RESULTS_XML" | head -1)
  TOTAL=$(grep -oP 'total="\K[0-9]+' "$RESULTS_XML" | head -1)
  INCONCLUSIVE=$(grep -oP 'inconclusive="\K[0-9]+' "$RESULTS_XML" | head -1)
  SKIPPED=$(grep -oP 'skipped="\K[0-9]+' "$RESULTS_XML" | head -1)
  echo "Results: $PASSED passed, $FAILED failed, $INCONCLUSIVE inconclusive, $SKIPPED skipped (total: $TOTAL)"
- if [ "$FAILED" != "0" ]; then
-   echo "::error::$FAILED test(s) failed"
-   exit 1
- fi
+ if [ -z "$FAILED" ]; then
+   echo "::error::Could not parse 'failed' count from test results XML — unexpected format"
+   exit 1
+ fi
+ if [ "$FAILED" -gt 0 ]; then
+   echo "::error::$FAILED test(s) failed"
+   exit 1
+ fi
🤖 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 @.github/workflows/unity-tests.yml around lines 124 - 133, The FAILED
extraction can be empty when the XML uses a different attribute name, causing
the test step to treat "" != "0" as true; update the grep that sets FAILED to
match both failed and failures attributes (e.g., use a regex like
(failed|failures)="\K[0-9]+") and then normalize the variable after extraction
with parameter expansion so it defaults to 0 when empty (FAILED=${FAILED:-0});
apply the same defensive defaulting for PASSED, TOTAL, INCONCLUSIVE, and SKIPPED
variables if desired, and keep the existing conditional if [ "$FAILED" != "0" ]
to fail only when FAILED is actually non‑zero.
🤖 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 @.github/workflows/unity-tests.yml:
- Around line 54-55: Replace use of production Unity credentials by provisioning
and referencing a dedicated CI-only Unity serial (rotateable) for the job that
runs game-ci/unity-test-runner@v4; ensure the job uses that CI-only secret
instead of any production UNITY_SERIAL/UNITY_LICENSE/UNITY_EMAIL/UNITY_PASSWORD
values and document the credential name in the workflow. Also add a clear
warning comment immediately above the safe-to-test label check (the label gating
logic that permits running pull_request_target checkouts) instructing
maintainers to manually inspect all tests and assets in the PR (including C#
test files) before applying the safe-to-test label, and reference this guidance
in CONTRIBUTING.md.

---

Outside diff comments:
In @.github/workflows/unity-tests.yml:
- Line 119: The find command that sets RESULTS_XML uses an unquoted expansion of
${{ steps.tests.outputs.artifactsPath }}, which will break if the path contains
spaces; update the command that assigns RESULTS_XML (the find invocation) to
quote the expansion so find sees the full path as a single argument (i.e., use a
quoted "${{ steps.tests.outputs.artifactsPath }}" in the find command and
preserve the rest of the flags/redirections).
- Around line 124-133: The FAILED extraction can be empty when the XML uses a
different attribute name, causing the test step to treat "" != "0" as true;
update the grep that sets FAILED to match both failed and failures attributes
(e.g., use a regex like (failed|failures)="\K[0-9]+") and then normalize the
variable after extraction with parameter expansion so it defaults to 0 when
empty (FAILED=${FAILED:-0}); apply the same defensive defaulting for PASSED,
TOTAL, INCONCLUSIVE, and SKIPPED variables if desired, and keep the existing
conditional if [ "$FAILED" != "0" ] to fail only when FAILED is actually
non‑zero.
🪄 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: d738ce59-502f-4485-86fb-c512a1518d2f

📥 Commits

Reviewing files that changed from the base of the PR and between 642687f and 7feaef4.

📒 Files selected for processing (3)
  • .github/workflows/unity-tests.yml
  • MCPForUnity/Editor/Tools/ManageScene.cs
  • MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs
  • MCPForUnity/Editor/Tools/ManageScene.cs

Comment on lines +54 to +55
ref: ${{ inputs.ref || github.event.pull_request.head.sha || github.ref }}
persist-credentials: false
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

UNITY credentials are exposed to untrusted fork code — use a dedicated CI-only serial and document the threat model

The checkout at line 54 resolves to github.event.pull_request.head.sha for pull_request_target events, placing fork-controlled C# test code on disk. That code is then executed by game-ci/unity-test-runner@v4 with UNITY_LICENSE, UNITY_EMAIL, UNITY_PASSWORD, and UNITY_SERIAL directly in the job environment. A malicious test could read those env vars and exfiltrate them (e.g., via an HTTP call in a [UnityTest]).

Combining pull_request_target with an explicit checkout of an untrusted PR is a dangerous practice. GitHub itself advises to avoid using this event if you need to build or run code from the pull request. The persist-credentials: false and contents: read correctly protect the GITHUB_TOKEN, but they offer no protection for the Unity-specific secrets passed to the test runner.

The PR explicitly acknowledges this trade-off and the label-gating is the right primary control, but two hardening steps would meaningfully reduce the blast radius:

  1. Use a dedicated, rotatable CI serial. Register a separate Unity serial used exclusively for CI. If it leaks it can be deactivated without impacting production licenses. This is a low-cost mitigation.
  2. Add a warning comment above the safe-to-test label check (or in CONTRIBUTING.md) instructing maintainers to inspect all test files—not just the production code—before adding the label.

Also applies to: 86-99, 101-114

🤖 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 @.github/workflows/unity-tests.yml around lines 54 - 55, Replace use of
production Unity credentials by provisioning and referencing a dedicated CI-only
Unity serial (rotateable) for the job that runs game-ci/unity-test-runner@v4;
ensure the job uses that CI-only secret instead of any production
UNITY_SERIAL/UNITY_LICENSE/UNITY_EMAIL/UNITY_PASSWORD values and document the
credential name in the workflow. Also add a clear warning comment immediately
above the safe-to-test label check (the label gating logic that permits running
pull_request_target checkouts) instructing maintainers to manually inspect all
tests and assets in the PR (including C# test files) before applying the
safe-to-test label, and reference this guidance in CONTRIBUTING.md.

@dsarno dsarno merged commit c298216 into CoplayDev:beta May 4, 2026
4 checks passed
dsarno added a commit that referenced this pull request May 5, 2026
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 #1103;
PR #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) <noreply@anthropic.com>
Lint111 added a commit to Lint111/unity-mcp that referenced this pull request May 5, 2026
Pulls upstream beta progress (versions 9.6.9-beta.4 → 9.6.9-beta.7) onto
our fork while preserving our local divergence (queue/gateway functionality
plus today's responsiveness fixes).

Substantive upstream changes pulled in:
- UnityCompatShims pattern + UnityPhysicsCompat / UnityAssembliesCompat
- UnityFindObjectsCompat 2022.3 compile-gap fix (CoplayDev#1106)
- game_view screenshot capture of UI Toolkit overlays
- Beta-release CI gating on test success (CoplayDev#1103)
- unity-tests CI trigger now includes MCPForUnity/Runtime/**

Conflicts resolved:
- PhysicsSettingsOps.cs — took upstream's UnityPhysicsCompat shim over our
  #pragma warning disable suppression (matches the new shim convention).
- UnityReflect.cs — kept both using directives (Editor.Services + Runtime.Helpers).
- Server/uv.lock — kept our version (9.6.8, matches pyproject.toml).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

Unity 2022.3 compile errors on beta 9.6.9-beta.5 / commit 37ef016

2 participants