Skip to content

fix: Hot reload asks for a retry, not a compile, when the Editor was only busy compiling or importing - #3219

Merged
hatayama merged 3 commits into
feature/hot-reload-large-project-feedback-2from
feat/hot-reload-asks-for-a-retry-after-the-editor-settles
Oct 7, 2026
Merged

hatayama merged 3 commits into
feature/hot-reload-large-project-feedback-2from
feat/hot-reload-asks-for-a-retry-after-the-editor-settles

Conversation

@hatayama

@hatayama hatayama commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • A hot-reload apply that fails only because the Editor is compiling or importing now says so in its response (RetryAfterEditorReady: true), so the CLI can wait for the Editor to settle and apply the same files again in the same command (the CLI side is the follow-up PR).
  • A request that arrives while the Editor is already compiling or importing is refused before the transform, instead of after it.
  • Every apply response now lists the scripts the run selected (SelectedFiles), so a retry can send exactly those files.

Aim

This is not a speed promise. When Unity's own compile (auto refresh) overlaps a hot-reload request, most of the time is spent waiting for that compile either way. What the retry protects is the Play session and the patches that are live: today such a refusal falls through to the fallback compile, which ends both. And if Unity's compile already took the edit in, the second apply finishes in seconds with NothingToApply, instead of a second, useless compile.

User Impact

  • Before: editing a script and sending uloop hot-reload right away often failed with "The Editor became busy before the reload could be applied: The Editor is compiling …". With --compile-on-skip auto, the CLI then ran a fallback compile; with off, the reader had to wait and run the command again.
  • After: the response marks this failure as one that passes once the Editor settles. Other failures (a failed last compile, a declaration the reader has to fix, a missing compiled assembly) are not marked, because waiting gives the same result.

Changes

  • RetryAfterEditorReady is true only when the kinds of all failed rows add up to exactly EditorNotReady. Any other kind mixed in leaves it false.
  • When a file is resolved and the Editor is compiling or importing, the file is refused right there, with a reason of its own. The checks that run earlier keep their own, more specific classification: assembly resolution, missing DLL, the MVID guard, and new-source membership. Two cases are not refused:
    • A file whose patches are already active and whose source is unchanged still reports AlreadyActive, because it has nothing to apply.
    • A failed last compile is not treated as busy. Waiting does not clear its errors, and the commit boundary keeps refusing it as before.
  • SelectedFiles holds the run's scripts as project-relative asset paths.
    • --files entries were kept as typed until now (./Assets/A.cs, absolute paths). They are normalized here, so explicit files and the changed files the run selected read the same way.
    • The retry sends this list as an explicit Files list. So the default selection's leave-out (a file that only adds enum members) does not apply on the second apply. This is accepted as a rare case.
  • HotReloadApplyResponseBuilder.Build takes the selected files. Its test-only shim passes an empty list, because its callers hold a result but not the selection.
  • output.md documents both fields.

Behavior by input

Editor state when the request arrives File Before After Test
compiling existing, changed transform, then refused at the commit boundary ("became busy") refused before the transform, RetryAfterEditorReady true Run_WhenTheEditorIsCompilingWhenTheRequestArrives_…
importing existing, changed the same, at the commit boundary refused before the transform, true Run_WhenTheEditorIsImportingWhenTheRequestArrives_…
compiling existing, applied and unchanged AlreadyActive unchanged, false Run_WhenAnUnchangedAppliedFileArrivesWhileTheEditorIsCompiling_StaysAlreadyActive
idle, then compiling at the commit boundary existing refused at the commit boundary the same, true Run_WhenTheEditorIsCompilingAtCommit_…
last compile failed existing refused at the commit boundary, fix advice the same, false Run_WhenTheLastCompileFailedAtCommit_…
compiling or importing new source refused by the new-source check the same check, the same kind existing HotReloadNewSourceMembershipTests (unchanged)
any missing DLL, stale or unloaded assembly their own classification unchanged; the refusal comes later existing HotReloadPatchTargetSupport*Tests (unchanged)
— failure kinds mixed with EditorNotReady — false HotReloadEditorReadyRetryTests

Verification

  • uloop compile: 0 errors.
  • uloop run-tests, run one regex at a time, all pass:
    • the new and changed classes (HotReloadEditorReadyRetryTests, HotReloadEditorStateSnapshotTests, HotReloadBusyEditorResponseE2ETests, HotReloadDefaultFilesTests): 35 tests
    • HotReloadPatchTargetSupportTests, HotReloadNewSourceMembership*, HotReloadRecommendedNextActionTests, HotReloadCompileFallbackDeciderTests, HotReloadApplyOutcomeTests, HotReloadPauseBeforeWiringWarningTests and HotReloadIntroducedTypeResponseTests: 132 tests
    • HotReloadToolTests and HotReloadIntroducedTypeActivationTests: 132 tests
  • scripts/check-file-length.sh (set to fail on findings): no findings. The CA1502 checker at 15: no findings. ResolvePatchTarget is at 14, with the refusal in a helper of its own.
  • check-skill-size: no SKILL.md over the limit. SKILL.md itself is not changed.
  • Mutations. Each was committed first, then applied alone; the four classes above were run, and the change was reverted. These tests failed:
Mutation Tests that failed
Decide accepts any set that contains EditorNotReady Decide_WithEditorNotReadyAndDeclaration_IsFalse, Decide_WithEditorNotReadyAndCompiledAssemblyMissing_IsFalse
GetBusyFailure also refuses a failed last compile GetBusyFailure_WhenOnlyTheLastCompileFailed_IsNull, Run_WhenTheLastCompileFailedAtCommit_KeepsTheFixAdvice
No refusal when the request arrives both …WhenTheRequestArrives… tests, and …CompilingAtCommit… (its first state read is idle and now lands on the commit boundary)
The builder does not write RetryAfterEditorReady …CompilingAtCommit… and both …WhenTheRequestArrives… tests
The refusal also hits an applied, unchanged file Run_WhenAnUnchangedAppliedFileArrivesWhileTheEditorIsCompiling_StaysAlreadyActive
The builder does not write SelectedFiles both …WhenTheRequestArrives… tests, and both HotReloadDefaultFilesTests asserts
The tool passes an empty list instead of its selection the two HotReloadDefaultFilesTests asserts only
  • An earlier version of Decide also took hasFailure. Removing that argument changed no test, so it was dropped. A failure kind is only ever set on a failed row, so a run with no failure always passes None.
  • This PR targets the integration branch, so the repository CI does not run on it. Every check above was run locally.

Review in cubic

…or was busy

When Unity's own compile (auto refresh) overlaps a hot-reload request, the
apply fails with "The Editor became busy", and the CLI then runs a fallback
compile that ends the Play session and every live patch. A refusal whose only
reason is a compile or an import in progress passes once the Editor settles,
so the response now says so and names the files it selected, letting the CLI
wait and apply the same files again in the same command.

- RetryAfterEditorReady is true only when every failure is EditorNotReady;
  any other kind mixed in gives the same result after a wait.
- SelectedFiles lists the run's scripts as project-relative asset paths,
  whether given as --files (kept as typed until now) or selected as the
  changed files, so the retry does not re-select an empty change set.
- A request that arrives while the Editor is compiling or importing is
  refused when the file is resolved, before the transform, with a reason of
  its own. A file whose patches are active and unchanged is left alone, and
  every earlier, more specific refusal keeps its classification. A failed
  last compile is not treated as busy: waiting does not clear its errors.
The builder sets a failure kind only on a failed row, so a run with no
failure always passes None, and None never equals EditorNotReady. The
hasFailure argument therefore never changed the answer: removing it left
every test passing.
…tput reference

A reader of a refused apply needs to know that the CLI waits and applies the
same files again, and that the retry sends them as an explicit list, so the
default selection's leave-out does not apply on the second apply.
@coderabbitai

coderabbitai Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 17 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Repository: hatayama/unity-cli-loop/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 29c46e15-b672-43d7-96a4-f67753817960
📥 Commits

Reviewing files that changed from the base of the PR and between c0fcd92 and 2073bf5.

⛔ Files ignored due to path filters (2)
  • Assets/Tests/Editor/HotReload/HotReloadEditorReadyRetryTests.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadEditorReadyRetry.cs.meta is excluded by none and included by none
📒 Files selected for processing (18)
  • .agents/skills/uloop-hot-reload/references/output.md
  • .claude/skills/uloop-hot-reload/references/output.md
  • Assets/Tests/Editor/HotReload/HotReloadBusyEditorResponseE2ETests.cs
  • Assets/Tests/Editor/HotReload/HotReloadDefaultFilesTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadEditorReadyRetryTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadEditorStateSnapshotTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadIntroducedTypeActivationTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadIntroducedTypeE2ETestBase.cs
  • Assets/Tests/Editor/HotReload/HotReloadIntroducedTypeResponseTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadPauseBeforeWiringWarningTests.cs
  • Assets/Tests/Editor/HotReload/HotReloadToolTests.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadApplyResponseBuilder.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadEditorReadyRetry.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadEditorStateSnapshotCapture.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadPatchTargetSupport.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/HotReloadTools.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Shared/HotReloadConstants.cs
  • Packages/src/Editor/FirstPartyTools/HotReload/Skill/references/output.md
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@hatayama
hatayama merged commit 9a271c5 into feature/hot-reload-large-project-feedback-2 Oct 7, 2026
5 checks passed
@hatayama
hatayama deleted the feat/hot-reload-asks-for-a-retry-after-the-editor-settles branch October 7, 2026 09:00
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.

1 participant