feat: Projects can run pinned CLI versions#1411
Conversation
Route the global uloop command through a dispatcher so each Unity project can run the real CLI version declared by its package pin. - Copy cli-pin.json into the project .uloop directory during Editor startup. - Build, package, download, and cache uloop-cli as the project-local executable. - Keep release metadata and pin contracts aligned through release-please config and tests.
Prefer uloop-cli over the legacy uloop archive entry so newly packaged releases do not cache the dispatcher as the real CLI. Also keep command help and leading-option errors in the dispatcher process before project pin resolution so no-project help paths keep working.
Reject non-release cliVersion values before resolving cache paths so project pins cannot escape the dispatcher cache. Keep launch in the dispatcher process because launch is the bootstrap command that opens Unity before a project pin may exist.
Raise the Unity setup minimum CLI release to the first dispatcher-capable release so clean installs do not bypass project pins. Tie the package pin's minimumDispatcherVersion to the installer minimum with a consistency test.
Update release-please so both the package pin and committed project pin receive release version bumps. Add a consistency test to prevent the project-level pin from shadowing package metadata with stale values.
Teach release-please to update minimumDispatcherVersion and the Unity installer minimum together with CLI releases. This keeps the first dispatcher-capable release as the install floor once the CLI release PR is stamped.
Keep ULOOP_DISABLE_SELF_UPDATE scoped to mutation only. When a project requires a newer dispatcher, fail with CLI_UPDATE_REQUIRED and ask for a manual update instead of bypassing the minimum-version contract.
Release-please CLI PRs stamp the next minimum CLI version before the matching tag exists. Keep the guard for ordinary PRs and rely on the release sync verification after merge for published release assets.
|
Warning Review limit reached
More reviews will be available in 25 minutes and 3 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
📝 WalkthroughWalkthroughAdds dispatcher-based startup for ChangesDispatcher pin sync and CLI rollout
Sequence Diagram(s)sequenceDiagram
participant UnityCliLoopEditorBootstrapper
participant CliPinSynchronizer
participant RunDispatcher
participant loadDispatcherPin
participant enforceDispatcherFreshness
participant resolveDispatcherRealCLI
participant runRealCLICommand
UnityCliLoopEditorBootstrapper->>CliPinSynchronizer: SyncCurrentProjectPin()
RunDispatcher->>loadDispatcherPin: loadDispatcherPin(projectRoot)
loadDispatcherPin-->>RunDispatcher: dispatcherPin
RunDispatcher->>enforceDispatcherFreshness: enforceDispatcherFreshness(pin)
enforceDispatcherFreshness-->>RunDispatcher: proceed or retryable error
RunDispatcher->>resolveDispatcherRealCLI: resolveDispatcherRealCLI(pin.CLIVersion)
resolveDispatcherRealCLI-->>RunDispatcher: real CLI path
RunDispatcher->>runRealCLICommand: runRealCLICommand(realCLI, args, stdout, stderr)
runRealCLICommand-->>RunDispatcher: exit code
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/build-and-test.yml:
- Around line 52-53: The protocol minimum-version check is too easy to bypass
because the workflow in the check step relies only on a branch-name prefix.
Update the condition on the “Check protocol minimum version bump” job to require
trusted release-please identity or metadata in addition to the existing
release-please--branches-- prefix, using the workflow’s pull_request context and
release-please-specific signals to distinguish real release-please PRs from
user-named branches.
In `@cli/internal/cli/dispatcher_download.go`:
- Line 23: The dispatcher download client currently uses http.DefaultClient,
which has no timeout and can stall startup; update dispatcherHTTPClient in the
dispatcher download flow to use an http.Client with a finite Timeout, and make
sure the download path that consumes it (the dispatcher release fetch logic)
uses that bounded client consistently. If there is a shared helper for CLI
downloads, apply the timeout there so all dispatcher download requests are
protected.
- Around line 169-172: The first-run download flow in dispatcherDownloadCLI can
remove a realCLIPath cache entry that another concurrent dispatcher has just
populated. Update the cleanup/replace sequence around realCLIPath and
tempRealCLIPath so it does not unconditionally delete an existing executable
before the rename; instead make the replacement atomic or only remove the temp
path after a successful swap. Use dispatcherDownloadCLI and the
os.Remove/os.Rename block to locate the fix.
In `@cli/internal/cli/dispatcher.go`:
- Around line 126-145: `minimumDispatcherVersion` is being accepted too loosely
in `readDispatcherPin`, which lets malformed values slip through and makes
`enforceDispatcherFreshness` skip the required update check. Add strict
semantic-version validation for `MinimumDispatcherVersion` in
`readDispatcherPin`, using the same parsing rules already applied to
`cliVersion`, and return an explicit error when the pin value is invalid. Keep
the existing freshness logic in `enforceDispatcherFreshness` unchanged so it
only runs with a validated minimum version.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: dfe2ea29-26f8-45cc-85fb-63e40d626e1b
⛔ Files ignored due to path filters (4)
.gitignoreis excluded by none and included by noneAssets/Tests/Editor/CliPinSynchronizerTests.cs.metais excluded by none and included by nonePackages/src/Editor/Infrastructure/CLI/CliPinSynchronizer.cs.metais excluded by none and included by nonePackages/src/cli-pin.json.metais excluded by none and included by none
📒 Files selected for processing (23)
.github/workflows/build-and-test.yml.uloop/cli-pin.jsonAssets/Tests/Editor/CliPinSynchronizerTests.csPackages/src/Editor/CompositionRoot/UnityCliLoopEditorBootstrapper.csPackages/src/Editor/Domain/CliConstants.csPackages/src/Editor/Infrastructure/CLI/CliPinSynchronizer.csPackages/src/Editor/ToolContracts/UnityCliLoopConstants.csPackages/src/cli-pin.jsonREADME.mdcli/cmd/uloop-cli/main.gocli/cmd/uloop/main.gocli/internal/cli/dispatcher.gocli/internal/cli/dispatcher_download.gocli/internal/cli/dispatcher_pin.gocli/internal/cli/dispatcher_test.gocli/protocol_version_consistency_test.gorelease-please-config.jsonscripts/build-go-cli.shscripts/package-go-cli.shscripts/test-protocol-minimum-version-workflow.shscripts/test-release-please-config.shscripts/verify-go-cli-dist.shscripts/verify-native-cli-release-assets.sh
There was a problem hiding this comment.
10 issues found across 27 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Only record dispatcher self-update checks after a successful update so transient optional failures remain retryable. Treat the project pin as authoritative while letting stale package pin candidates fall through to PackageCache or constants fallback. Keep dispatcher minimum versions manual, and guard cli-pin synchronization against unresolved package paths.
Harden dispatcher downloads with bounded HTTP requests and safer cache installation. Validate and normalize dispatcher pin versions before freshness checks. Use the Unity project root for cli-pin sync, tighten release workflow checks, and cover tar asset verification edge cases.
Summary
uloopcommand while running the CLI version pinned by the Unity package..uloop/cli-pin.jsoncontract so hand-run commands resolve the right project CLI.User Impact
uloopcommand, and project commands can download and cache the matchinguloop-cliautomatically.Changes
uloop-cliexecutable.Verification
scripts/check-go-cli.shscripts/check-code-complexity.shscripts/test-release-please-config.shscripts/test-protocol-minimum-version-workflow.shscripts/package-go-cli.sh && scripts/verify-native-cli-release-assets.shcli/dist/darwin-arm64/uloop compile --project-path <PROJECT_ROOT>cli/dist/darwin-arm64/uloop run-tests --project-path <PROJECT_ROOT> --test-mode EditMode --filter-type regex --filter-value ".*(CliSetupApplicationServiceTests|CliPinSynchronizerTests).*".uloop/cli-pin.json, restarted Unity withcli/dist/darwin-arm64/uloop launch <PROJECT_ROOT> -r, and confirmed the pin was regenerated and matchedPackages/src/cli-pin.json.