Summary
When a Multi-Platform Build & Publish for a world is started from the platform that is last in the SDK's platform order (for example Android, with Windows and Android ticked), it never finishes. It builds and uploads Android, builds and uploads Windows, then switches back to Android and builds and uploads it again, and keeps repeating until the user stops it.
The cause is
VRCMultiPlatformBuild.ShouldContinueMPB
. When the starting platform is also the last in the list, it returns
true
on every return to that platform, because that branch assumes the platform has not been built yet (as in the avatar flow). The world builder has already built it at the start, so the end-of-run check in
SetUpNextMPBTarget
(
MPBBuiltCount + 1 == MPBPlatformsList.Count
) can never become true.
Environment
  • Unity: 2022.3.22f1
  • VRChat Worlds SDK: com.vrchat.worlds 3.10.3
  • VRChat Base SDK: com.vrchat.base 3.10.3
  • Content type: World (Udon / UdonSharp)
  • Platforms ticked: Windows and Android (PC + Quest)
  • Editor open on: Android. The loop only happens when the run is started while on Android.
  • Files involved:
    VRCMultiPlatformBuild.cs
    (com.vrchat.base, Editor/VRCSDK/Dependencies/VRChat) and
    VRCSdkControlPanelWorldBuilder.cs
    (com.vrchat.worlds, Editor/VRCSDK/SDK3)
Steps to reproduce
  1. Open a world project with the Editor's active build target set to Android.
  2. Open the VRChat SDK Control Panel, Builder tab, and tick both Windows and Android for a multi-platform build.
  3. Press Build & Publish.
  4. Wait while the SDK builds and uploads Android, switches to Windows, builds and uploads Windows, and switches back to Android.
Expected:
the run ends when it arrives back on the starting platform, and the "Multi-Platform Upload Finished" notification shows.
Actual:
the SDK builds and uploads Android a second time, then Windows again, and so on until the user cancels.
Root cause
The platform list is
[Windows, Android]
and
MPBInitialPlatform
is Android, so the starting platform is also
MPBPlatformsList.Last()
. Tracing the session state through the run:
  1. Android (start), count 0:
    StartMPB
    sees initial == last, so it does not enter Building.
    BuildAndUploadMultiPlatform
    still builds and uploads Android because Android is in the list.
  2. Android, count 1:
    SetUpNextMPBTarget
    picks Windows, increments the count, state = Switching, switches platform.
  3. Windows, count 2:
    ShouldContinueMPB
    returns true (initial != current). Windows is built and uploaded.
    SetUpNextMPBTarget
    picks Android and increments.
  4. Android (back at start), count 2:
    ShouldContinueMPB
    reaches
    if (MPBInitialPlatform == MPBPlatformsList.Last()) return true;
    and returns true instead of finishing. Android is built and uploaded again.
  5. Android, count 3: the
    SetUpNextMPBTarget
    end check needs
    MPBBuiltCount + 1 == MPBPlatformsList.Count
    , which is 3 == 2 and false. It switches to Windows and increments again.
From step 5 the count only grows past the list length, so neither exit is ever reached: not the
isMPBFinished = true
path in
ShouldContinueMPB
, and not the early
return false
in
SetUpNextMPBTarget
.
The
initial == last
branch in
ShouldContinueMPB
and the
BuiltCount + 1 == Count
check in
SetUpNextMPBTarget
look designed as a pair for a flow where the starting platform is skipped at the beginning and built last (the code comment says this "can happen for Avatar builds"). In
VRCSdkControlPanelWorldBuilder.BuildAndUploadMultiPlatform
the only skip condition is
!_selectedBuildTargets.Contains(EditorUserBuildSettings.activeBuildTarget)
, so a starting platform that is in the list is built immediately. That leaves the count one ahead of what the end check expects. This pairing is an inference from reading the code, not something confirmed by running the avatar flow.
Suggested fix
End the run when it returns to the starting platform and every platform in the list has already been built. In
ShouldContinueMPB
, the
initial == last
branch should only return true while the starting platform has not been built yet:
```csharp
// If the initial platform is last in the list, only build it again if it has not been built yet
if (MPBInitialPlatform == MPBPlatformsList.Last() && MPBBuiltCount < MPBPlatformsList.Count)
{
return true;
}
```
With
[Windows, Android]
started on Android this gives
MPBBuiltCount == 2
on the return trip, so the run falls through to
isMPBFinished = true
and
ReportMPBDone()
. If the avatar flow counts differently (it may build the starting platform last and reach this point with a count of
Count - 1
), the same condition keeps that behaviour, but that is unverified.
A second option is to make
BuildAndUploadMultiPlatform
skip the starting platform when it is last in the list and call
SetUpNextMPBTarget(cancellationToken, false)
, so the count lines up with the existing end check.
Local patch used while waiting for an official fix:
an early exit at the top of
StartMultiPlatformBuild
in
VRCSdkControlPanelWorldBuilder.cs
. When
MPB
is true, the list has more than one platform, the initial platform is the last in the list and is the active target, and
MPBBuiltCount >= MPBPlatformsList.Count
, it calls
VRCMultiPlatformBuild.ReportMPBDone()
, re-enables the UI, shows the "Multi-Platform Upload Finished" notification and returns. This only touches the world builder, so avatar builds are unaffected. Tested: this patch was run in Unity on a real multi-platform publish started from Android, and worked. The run now finishes normally instead of looping. The one-condition change to
ShouldContinueMPB
above has not been run itself; the tested patch applies the same end-of-run rule from the world builder.
Workaround and evidence
Workaround without changing the SDK:
start the multi-platform publish while the Editor is on Windows, the first platform in the list. The run then goes Windows, Android, back to Windows, and the existing finish check works. After cancelling a looping run, use VRChat SDK > Utilities > Clear Multi Platform Build State, because the state lives in
SessionState
and survives script reloads.
Evidence from the Unity Editor.log of an affected run:
  • A platform switch to Windows was requested (
    Requested script compilation because: Switching to platform Windows, Mac, Linux
    ), followed later by a switch to Android (
    Switching to platform Android
    ).
  • Stack traces in the same log pass through
    VRCSdkControlPanelWorldBuilder.BuildAndUploadMultiPlatform
    (
    VRCSdkControlPanelWorldBuilder.cs
    , line 3312), confirming the build and upload ran from the multi-platform path.
  • The log covers a run that the user cancelled, so it shows the switch order but not a final repeat cycle. The repeat was observed directly in the Editor and is described above.
Happy to provide the full Editor.log, the Console output, or a screen recording of the loop if useful.