Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ So a `CancellationToken` named `ct` is always in scope; use it without declaring

## Long awaits block other tool commands

uloop runs one tool command at a time per Editor. While a snippet is awaiting, other tool commands against that Editor (such as `compile`, `get-logs`, or `run-tests`) are rejected as BUSY. An await that may never finish — waiting for an animation or a scripted sequence to complete, or for an external event — keeps the Editor BUSY for as long as it waits. `uloop status` still answers while a tool runs, so use it to check what is running. To stop a running snippet from another terminal, run `uloop cancel`.
uloop runs one tool command at a time per Editor. While a snippet is awaiting, other tool commands against that Editor (such as `compile`, `get-logs`, or `run-tests`) are rejected as BUSY. An await that may never finish — waiting for an animation or a scripted sequence to complete, or for an external event — keeps the Editor BUSY for as long as it waits. `uloop status` still answers while a tool runs, so use it to check what is running. To stop a running snippet from another terminal, run `uloop cancel`; the `uloop-status` skill's `references/cancel.md` lists its outcomes.

Another `execute-dynamic-code` is not rejected as BUSY: it returns `Success: false` with `ErrorMessage` "Another execution is already in progress". Since the next command an agent sends is often another snippet, treat that message as the sign that an earlier snippet is still awaiting.

Expand Down Expand Up @@ -59,6 +59,6 @@ uloop stops waiting for the snippet and frees the Editor for the next command. A

What the CLI that sent the snippet sees depends on why `ct` fired:

- Yielded to a foreground request, or stopped by `uloop cancel`: the connection is still open, so the CLI receives `Success: false` with `ErrorMessage` "Execution was cancelled or timed out".
- Yielded to a foreground request, or stopped by `uloop cancel`: the connection is still open, so the CLI receives `Success: false` with `ErrorMessage` "Execution was cancelled or timed out". While a command blocks Unity's main thread, the snippet cannot return, and `uloop status` alone never frees its slot. A tool command other than `execute-dynamic-code` frees it: the slot is taken back 5 seconds after that command first finds it held, and the CLI keeps retrying the busy command through those 5 seconds, so the command then runs.
- The CLI disconnected: no response is sent; the CLI that was waiting is already gone.
- Domain reload, Editor quit, or server stop/restart: no response arrives. The CLI treats it as a lost connection: after a domain reload it prints "Connection lost during execute-dynamic-code", waits for the Editor to come back, and then reports the disconnect as an error. Check the Editor with `uloop status` before sending the next command.
4 changes: 2 additions & 2 deletions .agents/skills/uloop-status/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: uloop-status
description: "Check whether the Unity Editor for this project can take a uloop command now, without side effects. Use before sending commands when Unity may be starting, compiling, busy, or closed."
description: "Check whether the Unity Editor for this project can take a uloop command now, without side effects; also covers `uloop cancel` for stopping the command it is running. Use before sending commands when Unity may be starting, compiling, busy, or closed, and when BUSY does not clear or a command does not finish."
---

# uloop status
Expand Down Expand Up @@ -52,7 +52,7 @@ Play Mode does not change the state: a paused Play Mode session is `Ready`, with
## Notes

- Exits 0 only for Ready.
- When `Busy` does not clear and the running command shows no sign of finishing, stop it with `uloop cancel`.
- When `Busy` does not clear and the running command shows no sign of finishing, stop it with `uloop cancel`; its outcomes, what it cannot cancel, and what to do after it are in [references/cancel.md](references/cancel.md).
- It never focuses the window, launches Unity, or takes the command slot, so it is safe to poll.
- A sandboxed shell that cannot open the Unix socket gets an error, not NotRunning; run it outside the sandbox.
- uloop cannot detect Safe Mode directly; ServerUnavailable that does not clear is the signal.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
---
name: uloop-cancel
description: "Cancel the uloop command the Unity Editor for this project is running now. Use when BUSY keeps rejecting commands or your own command does not finish."
---

# uloop cancel

Cancel the uloop command that holds the Editor's command slot, from another terminal or after the waiting command was given up on.

## When to Use

- Every command is rejected with `UNITY_SERVER_BUSY` and the running command shows no sign of finishing.
- Every command is rejected with `UNITY_SERVER_BUSY`, `uloop status` keeps reporting `Busy`, and the running command shows no sign of finishing.
- A command you started does not return, for example an `execute-dynamic-code` snippet that awaits something that never completes.

## Usage
Expand Down Expand Up @@ -49,15 +44,12 @@ A default EditMode `run-tests` and `compile --no-wait-for-domain-reload` can be

## What the Waiting Command Gets

- Most commands fail with `ErrorCode` `TOOL_CANCELLED` (not retryable).
- `execute-dynamic-code` returns its usual cancelled result: `Success: false` with "Execution was cancelled or timed out".
Most commands fail with `ErrorCode` `TOOL_CANCELLED` (not retryable). `execute-dynamic-code` instead returns its usual cancelled result; the `uloop-execute-dynamic-code` skill's `references/cancellation.md` describes it and what happens to a snippet that keeps running.

`Cancelled` also resumes Play Mode when the Editor is paused, whether a pause point or a manual pause holds it — the same as when the waiting CLI is killed.

## After Cancelling

Run `uloop status` and wait for `Ready` before the next command. `Cancelled` means the command was asked to stop, not that it already stopped:
Run `uloop status` and wait for `Ready` before the next command. `Cancelled` means the command was asked to stop, not that it already stopped: a tool that ignores its cancellation token keeps the slot until it returns on its own.

- A tool that ignores its cancellation token keeps the slot until it returns on its own.
- `execute-dynamic-code` returns at once and frees the slot, but a snippet that ignores `ct` keeps running in the background.
- While a command blocks Unity's main thread, `execute-dynamic-code` cannot return; its slot is freed about 5 seconds later, when another command arrives.
The exception is an `execute-dynamic-code` snippet that still cannot return, for example because a command blocks Unity's main thread. Polling `uloop status` never frees its slot, because status only reads the slot. Send a tool command other than `execute-dynamic-code` (such as `uloop get-logs`) instead: the slot is taken back 5 seconds after that command first finds it held, and the CLI keeps retrying the busy command through those 5 seconds, so the command then runs.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ So a `CancellationToken` named `ct` is always in scope; use it without declaring

## Long awaits block other tool commands

uloop runs one tool command at a time per Editor. While a snippet is awaiting, other tool commands against that Editor (such as `compile`, `get-logs`, or `run-tests`) are rejected as BUSY. An await that may never finish — waiting for an animation or a scripted sequence to complete, or for an external event — keeps the Editor BUSY for as long as it waits. `uloop status` still answers while a tool runs, so use it to check what is running. To stop a running snippet from another terminal, run `uloop cancel`.
uloop runs one tool command at a time per Editor. While a snippet is awaiting, other tool commands against that Editor (such as `compile`, `get-logs`, or `run-tests`) are rejected as BUSY. An await that may never finish — waiting for an animation or a scripted sequence to complete, or for an external event — keeps the Editor BUSY for as long as it waits. `uloop status` still answers while a tool runs, so use it to check what is running. To stop a running snippet from another terminal, run `uloop cancel`; the `uloop-status` skill's `references/cancel.md` lists its outcomes.

Another `execute-dynamic-code` is not rejected as BUSY: it returns `Success: false` with `ErrorMessage` "Another execution is already in progress". Since the next command an agent sends is often another snippet, treat that message as the sign that an earlier snippet is still awaiting.

Expand Down Expand Up @@ -59,6 +59,6 @@ uloop stops waiting for the snippet and frees the Editor for the next command. A

What the CLI that sent the snippet sees depends on why `ct` fired:

- Yielded to a foreground request, or stopped by `uloop cancel`: the connection is still open, so the CLI receives `Success: false` with `ErrorMessage` "Execution was cancelled or timed out".
- Yielded to a foreground request, or stopped by `uloop cancel`: the connection is still open, so the CLI receives `Success: false` with `ErrorMessage` "Execution was cancelled or timed out". While a command blocks Unity's main thread, the snippet cannot return, and `uloop status` alone never frees its slot. A tool command other than `execute-dynamic-code` frees it: the slot is taken back 5 seconds after that command first finds it held, and the CLI keeps retrying the busy command through those 5 seconds, so the command then runs.
- The CLI disconnected: no response is sent; the CLI that was waiting is already gone.
- Domain reload, Editor quit, or server stop/restart: no response arrives. The CLI treats it as a lost connection: after a domain reload it prints "Connection lost during execute-dynamic-code", waits for the Editor to come back, and then reports the disconnect as an error. Check the Editor with `uloop status` before sending the next command.
4 changes: 2 additions & 2 deletions .claude/skills/uloop-status/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: uloop-status
description: "Check whether the Unity Editor for this project can take a uloop command now, without side effects. Use before sending commands when Unity may be starting, compiling, busy, or closed."
description: "Check whether the Unity Editor for this project can take a uloop command now, without side effects; also covers `uloop cancel` for stopping the command it is running. Use before sending commands when Unity may be starting, compiling, busy, or closed, and when BUSY does not clear or a command does not finish."
---

# uloop status
Expand Down Expand Up @@ -52,7 +52,7 @@ Play Mode does not change the state: a paused Play Mode session is `Ready`, with
## Notes

- Exits 0 only for Ready.
- When `Busy` does not clear and the running command shows no sign of finishing, stop it with `uloop cancel`.
- When `Busy` does not clear and the running command shows no sign of finishing, stop it with `uloop cancel`; its outcomes, what it cannot cancel, and what to do after it are in [references/cancel.md](references/cancel.md).
- It never focuses the window, launches Unity, or takes the command slot, so it is safe to poll.
- A sandboxed shell that cannot open the Unix socket gets an error, not NotRunning; run it outside the sandbox.
- uloop cannot detect Safe Mode directly; ServerUnavailable that does not clear is the signal.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
---
name: uloop-cancel
description: "Cancel the uloop command the Unity Editor for this project is running now. Use when BUSY keeps rejecting commands or your own command does not finish."
---

# uloop cancel

Cancel the uloop command that holds the Editor's command slot, from another terminal or after the waiting command was given up on.

## When to Use

- Every command is rejected with `UNITY_SERVER_BUSY` and the running command shows no sign of finishing.
- Every command is rejected with `UNITY_SERVER_BUSY`, `uloop status` keeps reporting `Busy`, and the running command shows no sign of finishing.
- A command you started does not return, for example an `execute-dynamic-code` snippet that awaits something that never completes.

## Usage
Expand Down Expand Up @@ -49,15 +44,12 @@ A default EditMode `run-tests` and `compile --no-wait-for-domain-reload` can be

## What the Waiting Command Gets

- Most commands fail with `ErrorCode` `TOOL_CANCELLED` (not retryable).
- `execute-dynamic-code` returns its usual cancelled result: `Success: false` with "Execution was cancelled or timed out".
Most commands fail with `ErrorCode` `TOOL_CANCELLED` (not retryable). `execute-dynamic-code` instead returns its usual cancelled result; the `uloop-execute-dynamic-code` skill's `references/cancellation.md` describes it and what happens to a snippet that keeps running.

`Cancelled` also resumes Play Mode when the Editor is paused, whether a pause point or a manual pause holds it — the same as when the waiting CLI is killed.

## After Cancelling

Run `uloop status` and wait for `Ready` before the next command. `Cancelled` means the command was asked to stop, not that it already stopped:
Run `uloop status` and wait for `Ready` before the next command. `Cancelled` means the command was asked to stop, not that it already stopped: a tool that ignores its cancellation token keeps the slot until it returns on its own.

- A tool that ignores its cancellation token keeps the slot until it returns on its own.
- `execute-dynamic-code` returns at once and frees the slot, but a snippet that ignores `ct` keeps running in the background.
- While a command blocks Unity's main thread, `execute-dynamic-code` cannot return; its slot is freed about 5 seconds later, when another command arrives.
The exception is an `execute-dynamic-code` snippet that still cannot return, for example because a command blocks Unity's main thread. Polling `uloop status` never frees its slot, because status only reads the slot. Send a tool command other than `execute-dynamic-code` (such as `uloop get-logs`) instead: the slot is taken back 5 seconds after that command first finds it held, and the CLI keeps retrying the busy command through those 5 seconds, so the command then runs.
14 changes: 14 additions & 0 deletions Assets/Tests/Editor/ToolSkillSynchronizerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1491,6 +1491,7 @@ public void DetectTargets_WhenDeprecatedManagedSkillDirectoryExists_DoesNotRepor
Assert.That(detectedTargets[0].InstallState, Is.EqualTo(SkillInstallState.Installed));
}

// Tests that installing skills removes deprecated and retired skill directories from both layouts.
[Test]
public async Task InstallSkillFilesAtProjectRoot_WhenDeprecatedManagedSkillDirectoryExists_RemovesIt()
{
Expand Down Expand Up @@ -1528,6 +1529,17 @@ public async Task InstallSkillFilesAtProjectRoot_WhenDeprecatedManagedSkillDirec
"uloop-record-input");
WriteSkillFile(flatRetiredRecordInputSkillDir, "---\nname: uloop-record-input\n---\n");
WriteSkillFile(groupedRetiredRecordInputSkillDir, "---\nname: uloop-record-input\n---\n");
string flatRetiredCancelSkillDir = Path.Combine(
targetRoot,
SkillInstallLayout.SkillsDirName,
"uloop-cancel");
string groupedRetiredCancelSkillDir = Path.Combine(
targetRoot,
SkillInstallLayout.SkillsDirName,
SkillInstallLayout.ManagedSkillsDirName,
"uloop-cancel");
WriteSkillFile(flatRetiredCancelSkillDir, "---\nname: uloop-cancel\n---\n");
WriteSkillFile(groupedRetiredCancelSkillDir, "---\nname: uloop-cancel\n---\n");

ToolSkillSynchronizer.SkillTargetInfo target = new(
"Claude Code",
Expand Down Expand Up @@ -1557,6 +1569,8 @@ await ToolSkillSynchronizer.InstallSkillFilesAtProjectRoot(
Assert.That(Directory.Exists(executeMenuItemSkillDir), Is.False);
Assert.That(Directory.Exists(flatRetiredRecordInputSkillDir), Is.False);
Assert.That(Directory.Exists(groupedRetiredRecordInputSkillDir), Is.False);
Assert.That(Directory.Exists(flatRetiredCancelSkillDir), Is.False);
Assert.That(Directory.Exists(groupedRetiredCancelSkillDir), Is.False);
Assert.That(File.Exists(Path.Combine(installedSkillDir, SkillInstallLayout.SkillFileName)), Is.True);
Assert.That(File.ReadAllText(Path.Combine(installedSkillDir, "reference.md")), Is.EqualTo("reference"));
}
Expand Down
4 changes: 2 additions & 2 deletions Packages/src/Editor/CliOnlyTools~/Status/Skill/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: uloop-status
description: "Check whether the Unity Editor for this project can take a uloop command now, without side effects. Use before sending commands when Unity may be starting, compiling, busy, or closed."
description: "Check whether the Unity Editor for this project can take a uloop command now, without side effects; also covers `uloop cancel` for stopping the command it is running. Use before sending commands when Unity may be starting, compiling, busy, or closed, and when BUSY does not clear or a command does not finish."
---

# uloop status
Expand Down Expand Up @@ -52,7 +52,7 @@ Play Mode does not change the state: a paused Play Mode session is `Ready`, with
## Notes

- Exits 0 only for Ready.
- When `Busy` does not clear and the running command shows no sign of finishing, stop it with `uloop cancel`.
- When `Busy` does not clear and the running command shows no sign of finishing, stop it with `uloop cancel`; its outcomes, what it cannot cancel, and what to do after it are in [references/cancel.md](references/cancel.md).
- It never focuses the window, launches Unity, or takes the command slot, so it is safe to poll.
- A sandboxed shell that cannot open the Unix socket gets an error, not NotRunning; run it outside the sandbox.
- uloop cannot detect Safe Mode directly; ServerUnavailable that does not clear is the signal.
Expand Down
Loading
Loading