Repository navigation
fix: Hot reload sent while another uloop command runs waits for it instead of failing as busy - #3226
Conversation
…ut writing its failure Hot reload is about to wait for a busy Editor on its status instead of resending every second, which also brings the Editor to the front after five seconds. The send loop gets a flag that hands the first busy answer back, and runPlainTool is split so the sending half returns the failure for the caller to report. Every other tool keeps the bounded busy retry and the same stderr output.
…th UNITY_SERVER_BUSY A hot-reload sent right after a compile was refused because the compile still held the Editor, retried for ten seconds, brought the Editor to the front after five, and failed. Hot reload now takes the first busy answer, waits on the Editor status until it is ready (up to the same ten minutes as the settle wait), and sends the same request once. A second busy answer is reported without another wait. While a cancelled execute-dynamic-code holds the Editor, the wait resends every five seconds, because the Editor takes that slot back only when a tool request arrives. The settle wait that can follow now appends its note and adds its wait time to what the busy wait left, instead of overwriting them.
The hot-reload references, the vibe log list, and the single-flight notes in the agent instructions and the soak-testing guide said every command behind a running one fails with UNITY_SERVER_BUSY. Hot reload now waits for the running command and sends once, so they say so, name the two new log entries, and tell readers to give --files after a compile.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (17)
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughThe CLI now handles hot-reload requests that encounter a busy Unity Editor by waiting for readiness and retrying. The changes add response notes and timing, logging, tests, and updates to hot-reload guidance. ChangesHot-reload busy Editor handling
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to Hot reload’s busy-Editor wait is ready to merge after normal checks. A wait near its deadline may finish a few seconds late. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The wait changes when a request runs, not which project or operations it can access. Existing permission and execution checks remain in place, and no introduced security bypass was identified in the reviewed flow. Connection security and behavior across Editor restarts are not fully established. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 73.58% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 53 functions across 8 files. (9 skipped: 9 unsupported.)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
When the settle wait ran out, the note said how long the command waited but Timing did not carry that wait; after a busy wait, Timing held only the busy part while the note named both. The give-up path now adds its wait the same way a second apply does, and a test pins both the appended note and the summed wait on that path.
The never-settled test shared a 200 ms budget between the busy and settle waits, so a slow machine could run the budget out during the busy wait and fail the script, and its 280 ms threshold left little margin. The budget is now 400 ms and the test compares EditorReadyWaitMs with the sum of the two waits the vibe log records.
78f7ebb
into
feature/hot-reload-large-project-feedback-2
Summary
uloop hot-reloadsent while another uloop command (typicallyuloop compile) still holds the Editor no longer fails withUNITY_SERVER_BUSYafter ten seconds and no longer brings the Editor to the front. It waits for that command to finish and the Editor to be ready, then sends the same request once.The Editor runs one tool at a time: a request that arrives while another tool holds the slot is answered with a
server_busyerror, andcompilekeeps the slot until Unity's compile finishes. The CLI used to resend such a request every second for ten seconds and then report the last busy answer; after five seconds of busy answers it also brought the Editor to the front (thebusy_stallfocus). A hot-reload sent two seconds after a compile therefore failed every time, and the settle wait added in the previous change never ran, because no answer ever came back.Aim
This is not about speed: when a compile holds the Editor, most of the time goes into that compile either way. What the wait protects is the user's flow and the Play session: the command no longer fails, the Editor is not pulled to the front, and the edit is applied as a patch (or reported as
NothingToApplywhen the compile already took it in) instead of the user having to rerun it or fall back to another compile.User Impact
uloop compilefollowed byuloop hot-reload --files …exited 1 after about 10.8 seconds withUNITY_SERVER_BUSY, and the Editor came to the front during the wait.hot-reload: the Editor is busy running 'compile'; waiting for it to finish, then applying..., the command waits (up to 10 minutes, the same budget as the settle wait), sends once, and reports that answer withEditorReadyRetryNoteandTiming.EditorReadyWaitMs. The Editor is not brought to the front.Behaviour change
null, another RPC error, a transport failure)UNITY_SERVER_BUSY, exit 1; Editor brought to the front after 5 sUNITY_SERVER_BUSY, exit 1, no second waitexecute-dynamic-codeEvery other tool keeps the bounded busy retry and the
busy_stallfocus.Changes
The send loop has a
returnBusyWithoutRetryflag that hands the first busy answer back. Only hot reload sets it; its default is off.runPlainToolis split.sendPlainToolsends the request and returns the failure without writing it, so hot reload can decide what the user sees.runPlainToolstill writes the same failure, so its other callers are unchanged.New
hot_reload_busy_wait.go:execute-dynamic-codeholder: the Editor takes back a cancelled request's slot only when another tool request arrives, never on a status answer.The settle wait (
RetryAfterEditorReady) can run right after a busy wait. It now appends its sentence toEditorReadyRetryNoteand adds its time toTiming.EditorReadyWaitMs. Before, it overwrote both.EditorReadyWaitMseven without a busy wait, because its note already said how long the command waited.Two vibe log entries:
cli_hot_reload_busy_wait_decided(busy answers only) andcli_hot_reload_busy_wait_complete. The second is written once on every way out of a wait that started.Documentation updated:
scope-and-limits.md,output.md) and their generated copiesdocs/vibe-logs.mdAGENTS.mdanddocs/soak-testing.mdThe references also say to give
--fileswhen the command that ran was a compile, because with no files the Editor re-selects changed files, finds none, and fails validation.Input space
Axes: A = first answer, B =
runningToolNamein the busy data, C = the status answers seen during the wait, D = the answer after the wait, E = parameters.TestRunHotReload*null)WritesANonBusyErrorWithoutWaitingStillRetriesAnUndispatchedFailureWhenBusyReturnsAtOnce+ existingUNITY_SERVER_BUSY, focus at 5 sEditorReadyWaitMs, no focusWaitsForTheRunningCommandAndAppliesOnce,ReturnsTheFirstBusyAnswerWhenAskedWaitsForTheRunningCommandAndAppliesOnceEditorReadyWaitMsAppliesOnceMoreWhenTheBudgetEndsBeforeTheEditorIsReadyUNITY_SERVER_BUSY, exit 1, stdout emptyReportsACancelWhileWaitingForTheBusyEditorUNITY_SERVER_BUSY, exit 1ReportsASecondBusyAnswerWithoutWaitingAgainRetryAfterEditorReady: trueSelectedFilesKeepsBothNotesWhen…,AddHotReloadEditorReadyWaitMs…,ComposeHotReloadEditorReadyNote…FailsWhenTheApplyAfterTheBusyWaitIsNotAnObjectReportsATransportFailureOfTheApplyAfterTheBusyWaitanother uloop commandHotReloadBusyRunningToolNameFallsBack…PassesTheEditorAnswerThroughAfterTheBusyWaitWhenNoFilesWereGivenTiming)Status/RevertAllTimingcreatedStatusWaitsForTheRunningCommandTooCompileFallback: RequestedEditorReadyWaitMssurvive the mergeRunsTheFallbackCompileAfterTheBusyWait'execute-dynamic-code'SendsAgainWhileACancelledExecuteDynamicCodeHoldsTheEditorDoesNotSendAgainBeforeTheResendIntervalRetryAfterEditorReady: true, then the settle wait runs outEditorReadyWaitMsis both waits, exit 1, no compileKeepsBothNotesWhenTheEditorDoesNotSettleAfterTheBusyWaitExits of
sendHotReloadWaitingForBusyEditor_decided/_completesecond_resultfalse)second_resulttrue)execute-dynamic-codeholder, resend not busyresends≥ 1)Invariants:
execute-dynamic-code, this function sends at most two hot-reload requests, and the command sends at most three with the settle wait._decidedand_completeare each written exactly once.Verification
cli/project-runner:gofmt -l .: empty.go vet ./...andgolangci-lint run ./...: clean.cli/.golangci-complexity.yml): 0 issues.scripts/check-file-length.sh: no findings.check-skill-size: no SKILL.md over the limit;SKILL.mdis unchanged.scripts/sync-tool-docs.sh --check: the catalog matches.go test ./... -count=1: everything passes exceptTestSendWithTransientConnectionRetryAbortsOnRefusedConnect. That test cannot bind a Unix socket inside the local sandbox; it is untouched by this change and runs on CI.returnBusyWithoutRetry(back to the 1 s busy loop)ReturnsTheFirstBusyAnswerWhenAsked,WaitsForTheRunningCommand…and every other busy-wait scenarioWaitsForTheRunningCommand…,WritesBusyWaitVibeLogsand every other busy-wait scenarioWaitsForTheRunningCommand…,AppliesOnceMoreWhenTheBudgetEnds…, and 9 moreFilesdropped from the request after the waitWaitsForTheRunningCommand…,ReportsASecondBusyAnswer…,AppliesOnceMoreWhenTheBudgetEnds…,DoesNotSendAgainBeforeTheResendIntervalReportsASecondBusyAnswerWithoutWaitingAgainWaitsForTheRunningCommand…,AppliesOnceMoreWhenTheBudgetEnds…, and 6 moreEditorReadyWaitMsWaitsForTheRunningCommand…,AppliesOnceMoreWhenTheBudgetEnds…, and 3 moreTimingcreated on an answer without oneStatusWaitsForTheRunningCommandTooAppliesOnceMoreWhenTheBudgetEnds…ReportsACancelWhileWaitingForTheBusyEditor(through the vibe log: a request on a cancelled context fails at the dial, so stderr and the request count look the same)KeepsBothNotesWhen…,ComposeHotReloadEditorReadyNote…KeepsBothNotesWhen…,AddHotReloadEditorReadyWaitMs…addHotReloadTimingMsKeepsBothNotesWhen…(EditorReadyWaitMsbelow 20)_completenot written on a cancelReportsACancelWhileWaitingForTheBusyEditor_completewritten on success onlyReportsASecondBusyAnswerWithoutWaitingAgainWritesANonBusyErrorWithoutWaitingexecute-dynamic-codeholds the EditorSendsAgainWhileACancelledExecuteDynamicCodeHoldsTheEditorWaitsForTheRunningCommand…and 5 more (a request where the script expects a status)DoesNotSendAgainBeforeTheResendIntervalKeepsBothNotesWhenTheEditorDoesNotSettleAfterTheBusyWaitKeepsBothNotesWhenTheEditorDoesNotSettleAfterTheBusyWait,GivesUpWhenTheEditorDoesNotSettledistbinaries:Setup: a harmless edit in
<FILE>, thenuloop compile --force-recompilein the background and, two seconds later,uloop hot-reload --files <FILE> --compile-on-skip off --project-path <PROJECT_ROOT>.stderr carried
hot-reload: the Editor is busy running 'compile'; waiting for it to finish, then applying....stdout had
EditorReadyRetryNote= "The Editor was busy running 'compile' when this request arrived, so this command waited 31s for it to finish; …",Timing.EditorReadyWaitMs= 31430, andOutcome=NothingToApply(the compile had taken the edit in). The exit code was 0.The CLI vibe log had, in order:
cli_tool_request_failed(rpc:server_busy)cli_hot_reload_busy_wait_decided(running_tool_namecompile)cli_tool_request_sentcli_hot_reload_busy_wait_complete(readytrue,resends0,second_outcomeNothingToApply)There was no
cli_connection_retry_focus_attemptentry.gh workflow run build-and-test.yml --ref <branch>.Not changed
UNITY_SERVER_BUSY, and thebusy_stallfocus itself.