Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fe2ffda
fix: Hot reload no longer skips a property setter the edit did not to…
hatayama Oct 7, 2026
239efbb
perf: Hot reload reads each compiled assembly at most once per run wh…
hatayama Oct 7, 2026
175cd1c
fix: Hot reload's next-step advice fits an Editor that was busy or an…
hatayama Oct 7, 2026
8dacee7
fix: Hot reload's shim compile no longer sees internal types of assem…
hatayama Oct 7, 2026
e5dd3ab
fix: Hot reload's internal-member reason leads with the member and th…
hatayama Oct 7, 2026
c92eef2
fix: Hot reload finds the PDB document of a package source by the fol…
hatayama Oct 7, 2026
03df243
fix: Hot reload forgets a skipped file that went back to its compiled…
hatayama Oct 7, 2026
8937fd4
feat: Write CLI-side vibe log entries for plain tool commands and the…
hatayama Oct 7, 2026
c6dde73
chore: Share the script path mapping between hot reload and pause poi…
hatayama Oct 7, 2026
cd85209
fix: A compile that keeps Unity silent for 30 seconds is no longer re…
hatayama Oct 7, 2026
c0fcd92
fix: Pause points can now be set in package scripts, whichever path n…
hatayama Oct 7, 2026
9a23ed2
fix: Enabling a pause point no longer waits out the whole timeout whe…
hatayama Oct 7, 2026
60c9a93
fix: Skipped hot-reload rows name a package script by the path the ne…
hatayama Oct 7, 2026
9a271c5
fix: Hot reload asks for a retry, not a compile, when the Editor was …
hatayama Oct 7, 2026
8714925
fix: Pause point status, await, and clear find a marker whichever pat…
hatayama Oct 7, 2026
6e9c832
fix: Hot reload waits for a busy Editor and applies again instead of …
hatayama Oct 7, 2026
e208b53
fix: Pause point status, await, and clear find a marker in a local pa…
hatayama Oct 7, 2026
ef9991c
test: Expect the shared script path assembly among the Infrastructure…
hatayama Oct 7, 2026
78f7ebb
fix: Hot reload sent while another uloop command runs waits for it in…
hatayama Oct 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .agents/skills/uloop-hot-reload/references/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Returns JSON with:
- `ActivePatchTotal` (number): Active changes after this run — patched methods plus added members. Introduced types are not counted here; `ActiveIntroducedTypeTotal` reports those. `--revert-all` clears the patched methods and added members counted here and reports their combined count in `ClearedCount`; introduced types stay loaded until the next Domain Reload and remain in `ActiveIntroducedTypeTotal`. Does not include `AddedField` rows. Validation failures (`HOT_RELOAD_NO_CHANGED_FILES` and the other `ErrorCode` cases) also report the live ledger value, not the default 0.
- `AutoRefreshHeld` (boolean): True while Auto Refresh is held because at least one hot-reload change is still active. The first apply that arms the hold appends a Message sentence telling the caller to run `uloop compile` to release it, and that `--revert-all` releases it only when no introduced type remains — a revert cannot unload the assembly carrying an introduced type. A release during Play adds a Warning that pending script edits import on the next focus return or `uloop compile`. If the post-release Refresh is skipped because an open dirty scene also changed on disk, Warnings include the sentence telling the caller to resolve that scene and then run `uloop compile`. `--status` and `--revert-all` report the live value.
- `AutoRefreshHoldMessage` (string, optional): The hold sentence this run appended to `Message` when it armed the hold; omitted when the run did not arm it. After a successful fallback compile, the CLI removes that sentence from `Message` and keeps this field as the record of what it removed.
- `Timing` (object, apply runs only): milliseconds this run spent per phase — `AnalysisMs` (transform worker runs, introduced-type preparation included), `ShimCompileMs` (signature gate and shim compile, isolation retries included), `PatchMs` (patch application), `OtherMs` (`TotalMs` minus the three phases: file resolution, planning, and the checks that find unchanged methods), `TotalMs` (the whole run inside the Editor). When the CLI ran the fallback compile, it adds `FallbackCompileMs`, the wall time of that compile, success or failure. Omitted on `--status` and `--revert-all`.
- `Timing` (object, apply runs only): milliseconds this run spent per phase — `AnalysisMs` (transform worker runs, introduced-type preparation included), `ShimCompileMs` (signature gate and shim compile, isolation retries included), `PatchMs` (patch application), `OtherMs` (`TotalMs` minus the three phases: file resolution, planning, and the checks that find unchanged methods), `TotalMs` (the whole run inside the Editor). When the CLI ran the fallback compile, it adds `FallbackCompileMs`, the wall time of that compile, success or failure. When it waited for the Editor — for another uloop command to finish before the first apply, or for the Editor to settle, whether or not a second apply followed — it adds `EditorReadyWaitMs`, the wall time of all such waits in this command; the Editor's phases are then the last apply's. Omitted on `--status` and `--revert-all`.
- `AddedFieldTotal` (number): Live added-field ledger rows after this run or on `--status`, added field-like events included. Those rows appear as `Kind` `AddedField` on `--status` only; they are not counted in `ActivePatchTotal`
- `DroppedByPlayModeEntryCount` (number): Remaining patched-method, added-member, and introduced-type identities discarded by the Play-entry domain reload that have not been recovered by a later apply (`Patched` / `Added` methods, `Introduced` / `AlreadyActive` types — a recovered type also recovers the patches and added members inside it), `--revert-all`, or a successful compile. Omitted when the count is 0. Re-apply `uloop hot-reload`, or edit the files and run `uloop compile`
- `RestoredWiredValueCount` (number, `--status` only): Values written through the added-field wiring call that the last scene reload (entering or leaving Play Mode with domain reload disabled) gave back to the rebuilt objects. It also counts the values `--status` itself gave back by reading them for a host that is back at its place. Omitted when 0. See [added-field-wiring.md](added-field-wiring.md)
Expand All @@ -38,7 +38,10 @@ Returns JSON with:
- `IntroducedTypes` (array): Per-type `{ Kind, TypeName, AssemblyName, FilePath, Reason }` rows for the type declarations a reload met, always present and empty when there are none. On apply runs `Kind` is `Introduced` (this reload compiled the declaration into a retained assembly and it is now loaded), `AlreadyActive` (the declaration is bound from an assembly an earlier reload retained, so this reload introduced nothing for it), or `Failed` (the declaration was refused — a redefinition of a type already active, the same type declared in more than one file of the group, or a failed artifact compilation; `Reason` says which, and a `Failed` row alone makes `Success` false). On `--status` every row is `Kind` `Active` and lists a type this domain still holds. Declarations a reload simply cannot introduce are reported as `Warnings`, not rows. These rows are never counted in `PatchedTotal`, `ActivePatchTotal`, `AddedFieldTotal`, or `ClearedCount`
- `ActiveIntroducedTypeTotal` (number): Introduced types this domain holds after this run or on `--status`, counted per type rather than per compiled artifact; always present and `0` when there is none. `--revert-all` cannot unload them, so its Message says how many stay loaded until the next Domain Reload, and that Auto Refresh stays held for them until `uloop compile`
- `Message` (string): Short summary. When a run carries `IntroducedTypes` rows, Message reports them: a run that only introduced or only re-bound types says so instead of reporting the methods, a refused declaration is reported as the failure of the run and points at `IntroducedTypes`, and a run the methods decided ends with `IntroducedTypes=N`. On apply runs that pulled in sibling files, Message follows `PatchedTotal` and `Added` with how many of those Patched and Added rows re-applied the siblings' earlier changes (left out when 0). On apply runs, Message counts the patched rows that carry a `LifecycleNote` in one sentence and the added Unity messages a hot-reload proxy delivers in another, both pointing at `Methods[].LifecycleNote`; forwarded `Added` rows are not in the patched count, and each sentence is left out when its count is 0. On `--status`, Message opens with how many changes are currently active — patched methods, added members, and introduced types together, which is why it can exceed `ActivePatchTotal` — and when any `Active` or `Added` row has `InvocationCount` 0 it also appends how many such rows there are and points at `Methods[].Reason`. `--revert-all` appends how many introduced types stay loaded until the next Domain Reload, and when the hold is still armed for them, that Auto Refresh stays held until `uloop compile`. When the fallback compile succeeds, the CLI sets `Outcome` to `ReplacedByCompile` and `AutoRefreshHeld` to `false`, and removes the hold sentence (`AutoRefreshHoldMessage`) from `Message`, because the compile released the hold.
- `RecommendedNextAction` (string): Present in three cases. (1) Any method or introduced-type outcome is `Failed`: a partial apply (some methods patched or added, or some types left active) says to fix and rerun, run `uloop compile`, or `uloop hot-reload --revert-all`; a failure with nothing applied says to fix and rerun or compile. (2) Every method of the requested files was `Skipped`, which still answers `Success`: it points first at the fix each Skipped row's `Methods[].Reason` names and offers `uloop compile` as the alternative. (3) `CompileFallback` is `HeldForPlayMode` or `BlockedByPlayModeSetting`, whatever the outcomes: the reason no compile ran is appended after any advice from (1) or (2), and it opens by saying to do any fix a `Reason` names that needs no compile before compiling. Omitted otherwise.
- `RecommendedNextAction` (string): Present in three cases. (1) Any method or introduced-type outcome is `Failed`. A failure that needs a change in the source keeps the fix advice: a partial apply (some methods patched or added, or some types left active) says to fix and rerun, run `uloop compile`, or `uloop hot-reload --revert-all`; a failure with nothing applied says to fix and rerun or compile. When every failure came from the Editor compiling or importing while the reload ran, it says there is nothing to fix: wait until `uloop status` no longer reports that, then rerun, or run `uloop compile` (after a partial apply, `uloop hot-reload --revert-all` is offered as well); beside failures that need a fix, the fix advice comes first and adds that those rows need only a rerun. A missing compiled assembly adds that there is nothing to fix in the source and that `uloop compile` is the step; in a Multiplayer Play Mode Virtual Player, that compile has to run against the main Editor's project, because a patch applied in the main Editor does not reach the player. (2) Every method of the requested files was `Skipped`, which still answers `Success`: it points first at the fix each Skipped row's `Methods[].Reason` names and offers `uloop compile` as the alternative. (3) `CompileFallback` is `HeldForPlayMode` or `BlockedByPlayModeSetting`, whatever the outcomes: the reason no compile ran is appended after any advice from (1) or (2), and it opens by saying to do any fix a `Reason` names that needs no compile before compiling. Omitted otherwise.
- `CompileFallback` (string, always present): whether the CLI should run a compile after this run — `NotNeeded`, `Requested`, `HeldForPlayMode` (edits stayed unapplied but the Editor is in Play Mode and `--compile-on-skip` is `auto`), `BlockedByPlayModeSetting` (`--compile-on-skip on` during Play Mode while Unity's "Script Changes While Playing" is "Recompile After Finished Playing", which refuses the compile; `RecommendedNextAction` says to stop Play Mode first), or `Disabled` (`--compile-on-skip off`). `--status`, `--revert-all` and validation failures answer `NotNeeded`. `Skipped` rows of a sibling pulled in to re-bind its active patches do not count as unapplied edits: they are not this run's edits, and their earlier patches stay active. Its `Failed` rows do count, because a failed reload reverts those patches. A sibling retried after an earlier Skip, or brought in as a companion, still counts.
- `RetryAfterEditorReady` (bool, apply runs only): `true` when every failure of the run is the Editor compiling or importing, whether the request arrived during the compile or the compile started before the reload could be applied. The CLI then waits for the Editor to settle (up to 10 minutes) and applies the same files again in the same command, so the response you read is the second apply's. `false` on every other failure, and on `--status` and `--revert-all`.
- `SelectedFiles` (string[], apply runs only): the project-relative asset paths of the scripts the run selected, whether given as `--files` or chosen as the changed files. The retry sends exactly these, as an explicit list, so a file the default selection would leave out is not left out on the second apply. Empty on `--status` and `--revert-all`.
- `Compile` (object, present only when the CLI ran the fallback compile): the full `uloop compile` response; the top-level `Success` is then the compile's, and the command's exit code is the compile's. A successful compile drops `RecommendedNextAction`, sets `Outcome` to `ReplacedByCompile` and `AutoRefreshHeld` to `false`, removes the hold sentence from `Message`, and ends `Message` with a sentence saying the compile succeeded; a failed one leaves `Outcome`, `AutoRefreshHeld`, and `Message` as the reload reported them, and sets `RecommendedNextAction` to the compile's own `NextActions` when it reports any, and otherwise to fixing `Compile.Errors`.
- `CompileFallbackNote` (string, present only with `Compile`): why the compile ran and how it ended. When it succeeded, `Outcome` is `ReplacedByCompile`, `AutoRefreshHeld` is `false`, and `Message` no longer carries the hold sentence.
- `EditorReadyRetryNote` (string, added by the CLI): present when the command waited for the Editor before sending: because another uloop command held the Editor when the request arrived (the note names that command; `--status` and `--revert-all` wait the same way and carry the note, without `Timing`), or because `RetryAfterEditorReady` was `true`. It says what the command waited for and that every other field describes the answer that followed; or that the Editor did not settle within the wait, so the fields are the earlier apply's and no compile ran. When the command waited more than once, the note carries one sentence per wait in order. A second apply that is refused again is not retried a third time: its `CompileFallback` decides the compile as usual.
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,8 @@ source on disk. When a run skips a method it had patched before, `Warnings` name
| Resolved assembly name is missing from CompilationPipeline | Per-file entry with `Method` = `(file)`; Unity may have mapped a not-yet-imported `.asmdef` onto a predefined assembly. Run `uloop compile` first |
| Script is not in the last compiled assembly's source list and its assembly membership cannot be confirmed | Per-file entry with `Method` = `(file)`; a new file passed with `--files` is hot-reloadable when its membership in an existing, unchanged compiled assembly is confirmed (`.asmdef` / `.asmref` boundaries are checked when present; a predefined assembly with none also passes), but fails when the Editor is not ready or an `.asmdef` / `.asmref` on its path was added, deleted, or changed since the last import — run `uloop compile` first |
| The Editor is a Multiplayer Play Mode Virtual Player | Per-file entry with `Method` = `(file)`; a Virtual Player has no compiled assemblies under its own project root, so hot reload cannot patch it yet. The edit reaches that player only through a compile: the CLI's compile fallback brings it in when `--compile-on-skip` lets the compile run, and `auto` holds it while that player is in Play Mode (`CompileFallback` is `HeldForPlayMode`). A patch applied to the main Editor does not reach Virtual Players (each is a separate Editor process) |
| The Editor is compiling or importing when the request arrives, or starts to before the reload is applied | Per-file entry with `Method` = `(file)`; nothing in the source needs a change. The response sets `RetryAfterEditorReady`, and the CLI waits for the Editor to settle (up to 10 minutes) and applies the same request again once; if Unity's own compile already took the edit in, the second apply reports `NothingToApply`. A second refusal falls through to `--compile-on-skip`. |
| Another uloop command still holds the Editor when the request arrives (for example a `uloop compile` sent a moment earlier) | The request is not run; the CLI does not retry against the busy Editor and does not bring it to the front. It waits for that command to finish and the Editor to be ready (up to 10 minutes), then sends the same request once. `EditorReadyRetryNote` names the command it waited for and `Timing.EditorReadyWaitMs` is the wait. A busy answer to that one request is reported as `UNITY_SERVER_BUSY` without another wait. While a cancelled `execute-dynamic-code` request still holds the Editor, the wait sends the request again every 5 seconds, because the Editor takes such a request's slot back only when another request arrives. When the command that ran was a compile that already took the edit in, give `--files`: with no files the Editor selects changed files anew, finds none, and fails validation instead of reporting `NothingToApply` |
| Loaded assembly differs from the one on disk (pending compile) | Run `uloop compile` first, then retry |
| Source file fails to parse | Per-file `Failed` entry with `Method` = `(file)` carrying the parse errors; nothing from that file is applied, its earlier patches stay active, and `Success` is false |
| Method signature not found in the loaded assembly | Usually a stale assembly; run `uloop compile`. In-file renames and signature changes are classified as added members before reaching this point |
Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/uloop-pause-point/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Enable a pause point so Unity pauses when that code path is reached, either by a
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `--id` | string | - | Named pause point id passed to UloopPausePoint.Pause. Mutually exclusive with File/Line |
| `--file` | string | - | Project-relative source file path to patch a pause point into. Requires Line; mutually exclusive with Id |
| `--file` | string | - | Source file to patch a pause point into: `Assets/...`, `Packages/<package-id>/...`, a package folder path, or an absolute path. Requires Line; mutually exclusive with Id |
| `--line` | integer | - | 1-based source line to resolve within File. Requires File; mutually exclusive with Id |
| `--timeout-seconds` | integer | `30` | Seconds before the enable request expires and stops pausing late hits |
| `--mode` | enum | `single-shot` | Capture mode: single-shot pauses once, continuous pauses on every hit, trace records hits without pausing |
Expand Down Expand Up @@ -95,7 +95,7 @@ On a wait timeout, `PAUSE_POINT_EXPIRED`, or an enable failure, read `Error.Deta
- Patches drop on every compile or domain reload (the compile / Play-entry responses warn). `--persist` re-arms automatically (`references/persist.md`).
- Physics message methods, their helpers, and pre-bound delegates can miss hits on pre-existing GameObjects; enable warns where detectable.
- An `--id` marker waits on your `UloopPausePoint.Pause(id)` call (`using io.github.hatayama.UnityCliLoop.Runtime;`); its hits record no `CapturedVariables`.
- For scripts under `Packages/`, pass the package-id path form (`Packages/<package-id>/...`); physical checkout paths do not resolve.
- Every `--file` form resolves to the asset path (`Packages/<package-id>/...` for a package script), which the marker `Id` uses; status, await, and clear find it by any form.

## Reference Guides

Expand All @@ -109,4 +109,4 @@ Read the one whose trigger matches:
- `references/condition-triggered-pause.md` — runtime-condition pauses.
- `references/fast-progressing-games.md` — freezing self-progressing games, `--resume-play`.
- `references/persist.md` — what `--persist` restores, timeout restart, first-frame gap, the re-arm report.
- `references/troubleshooting.md` — timeouts, missed hits, hot reload, Debug switch, failure codes.
- `references/troubleshooting.md` — timeouts, missed hits, hot reload, Debug switch, failure codes, path forms.
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ If enable fails with a "No sequence point found" error (worded "No compiled stat

A "No method named '<name>' ..." failure means no method matching `--method` has a statement on or after `--line`. The match is case-sensitive, on the simple name or `Type.Method`, and a name that matches no method in the file fails on every line and after every compile, so check `--method` (or drop it) before moving `--line`.

## File Path Forms

`--file` takes the asset path Unity shows (`Assets/...` or `Packages/<package-id>/...`), the path under a package's folder (`Packages/<folder>/...` for an embedded package), or an absolute path. Every form resolves to the asset path, and a file:line marker's `Id` is `<asset path>:<line>`. `pause-point-status`, `await-pause-point`, and `clear-pause-point` resolve `--file` (or an `--id` of the form `<path>:<line>`) the same way, so every form finds the same marker. A named marker's `Id` is looked up as given.

## Debug-switch trade-offs

The warning that the Debug switch happened means the pause point is already armed: do not
Expand Down
Loading
Loading