Skip to content

fix: Background Unity editors no longer pop over other windows and keep serving commands - #2029

Merged
hatayama merged 5 commits into
v3-betafrom
feat/always-on-autotick-pump
Jul 27, 2026
Merged

fix: Background Unity editors no longer pop over other windows and keep serving commands#2029
hatayama merged 5 commits into
v3-betafrom
feat/always-on-autotick-pump

Conversation

@hatayama

@hatayama hatayama commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Background Unity Editors driven by uloop no longer pop their window over whatever you are working in — neither when a command has to rescue a stalled editor, nor when Play Mode starts or resumes from a pause point.
  • Unfocused editors keep responding to CLI commands instead of being parked by the OS and timing out.

User Impact

  • Before: an idle unfocused editor could be parked by macOS, so the next command hit a connection timeout and the CLI force-focused Unity to recover — and even without that, every Play Mode start and every pause-point resume made Unity raise its own window above other apps. Running multiple agent-driven projects meant editors constantly jumping into view.
  • After: a lightweight always-on tick keeps unfocused editors serving commands (no more rescue focus in normal operation), and while the editor is unfocused the native "focus the Game view on play/resume" raise is suppressed. When you focus Unity yourself, the stock behavior is restored untouched.
  • The launch/focus-window skills no longer bait agents into running uloop launch as a health check after a single failed command, which was another source of surprise foregrounding.

Changes

  • Auto-tick pump is now always-on at ~60Hz (matching the com.unity.pipeline precedent) instead of request-scoped with a trailing window; the scope controller and its tests are removed.
  • New focus-gated suppression: while the editor is unfocused, Game views set to "Play Focused" are switched to "Play Unfocused" and restored on focus return. The flag persists in EditorUserSettings so a crash while unfocused still restores the user's setting. Wired via focusChanged plus a 0.5s reconcile, same pattern as the existing Auto Refresh hold.
  • Connection-retry rescue in the project runner now vibe-logs the focus-restore outcome (success / failed / skipped / unavailable) under the same correlation ID as the focus attempt.
  • Launch and focus-window skill wording tightened so agents retry a failed command instead of relaunching (and thereby foregrounding) Unity.
  • Known limits (accepted): with more than one play-mode view open (extra Game view / Device Simulator — a rare setup), the restore on focus return also flips views that were already "Play Unfocused" to "Play Focused"; single-Game-view setups round-trip exactly. Views set to "Play Maximized" are out of scope — Unity's maximize path always focuses them.

Verification

  • dist/darwin-arm64/uloop compile — 0 errors, 0 warnings.
  • uloop run-tests --filter-type regex --filter-value PlayModeFocusSuppressionServiceTests — 11/11 passed; Go side scripts/check-go-cli.sh for the runner changes.
  • Empirical z-order measurement (CGWindowList) on a background editor: before the fix, Play enter and pause→resume raised the Unity window; with the fix, no raise across Play/Pause/Resume/Stop, and focusing Unity restores "Play Focused" (confirmed via play_focus_suppress_armed/released vibe logs).
  • Overnight run with three agent-driven projects: after the always-on pump, zero cli_connection_retry_focus_attempt events (previously the editors needed rescue focus after ~15 min idle).

Review in cubic

hatayama added 5 commits July 27, 2026 21:18
The scoped pump (in-flight request plus a 10s trailing window) let an
unfocused editor go fully idle once the window expired; macOS then
stopped scheduling the process, so the next IPC request could not even
be accepted (pre_accept_timeout) and the CLI had to grab OS-level focus
to wake Unity. Keep the 16ms SignalTick pump running for the whole
editor session instead, matching com.unity.pipeline's AutoTickCommand
semantics, so the process never gets parked.

- Remove BeginScope/AutoTickPumpController and their tests; the pump no
  longer needs scope tracking or TRAILING_WINDOW_SECONDS
- Drop the BeginScope wrapper in JsonRpcRequestProcessor
The launch skill description said "Use when Unity is not running or
unresponsive", which agents read as "run launch after any failed
command". In a real incident, one cancelled command (domain-reload
startup protection) led an agent to run uloop launch on an already
running Editor, which focused the Unity window and yanked it in front
of the user.

Narrow the description to "not running or stays frozen after retries",
state that launch focuses the Unity window as a side effect, and add a
"When not to use" section telling agents to retry a single failed,
cancelled, or busy command instead of launching. The focus-on-running
behavior itself is unchanged by design.

Generated skill copies under .claude/ and .agents/ are regenerated;
sync-tool-docs reports no catalog drift (launch is CLI-only and not in
default-tools.json).
The bullet read as "when Unity seems unresponsive, bring it to front",
inviting the same focus-stealing reflex the previous commit removed
from the launch skill. Remove the claim without a replacement; the
frontmatter description already scopes the tool to visual checks and
user-facing interaction.

Generated skill copies under .claude/ and .agents/ are regenerated;
sync-tool-docs reports no catalog drift (body-only change).
The focus-grab recovery logged every focus attempt but nothing about
the restore, so an investigation could not tell whether the restore
ran and succeeded, ran and failed, was intentionally suppressed by
keepUnityFocusedAfterReturn, or never had a restorer because the
previous frontmost PID could not be read.

- Add cli_connection_retry_focus_restore_success/_failed/_skipped/
  _unavailable vibe log operations covering those four outcomes
- Reuse the focus attempt's correlation ID (stored on the controller
  with the pid and triggering reason) so attempt and restore outcome
  can be joined in the log
- Keep restore() silent when focus never happened, so ordinary
  commands write no extra log lines
- Move the focus vibe-log writers to connection_retry_focus_log.go to
  keep connection_retry.go under the 500-line architecture cap
Unity's EditorApplicationLayout raises the Editor window above other
apps whenever Play Mode starts or resumes from pause, unless the play
mode view is set to PlayUnfocused. For background editors driven by the
uloop CLI this pops Unity over the user's windows on every pause-point
resume.

While the Editor is unfocused, force every PlayFocused PlayModeView to
PlayUnfocused; restore them to PlayFocused when focus returns. Views the
user set to PlayMaximized or PlayUnfocused are left alone by the
suppress direction.

- PlayModeViewFocusBridge uses friend access to PlayModeView directly
  (no reflection) and only flips PlayFocused<->PlayUnfocused, which the
  setter handles without the PlayMaximized cascade
- PlayModeFocusSuppressionService is pure C# with injected delegates and
  is covered by EditMode unit tests
- The suppressed flag persists in EditorUserSettings so a crash or
  restart while unfocused still restores views on the next focus
- A 0.5s-throttled reconcile self-heals after domain reloads and
  background launches where focusChanged never fires, and re-suppresses
  Game views opened while still unfocused
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds focus-gated Play Mode suppression with persisted reconciliation, changes Unity’s editor tick pump to always-on behavior, adds correlated connection-retry focus/restore logs and tests, and updates focus-window and launch skill guidance.

Changes

Play Mode focus suppression

Layer / File(s) Summary
Suppression state machine and validation
Packages/src/Editor/FirstPartyTools/ControlPlayMode/*, Assets/Tests/Editor/PlayModeFocusSuppressionServiceTests.cs
Adds injected focus suppression/restoration state handling, persisted flags, reconciliation, operation logging, and NUnit coverage for focus transitions and persistence.
Editor integration and Play Mode view bridge
Packages/src/Editor/InternalAPIBridge/PlayModeViewFocusBridge.cs, Packages/src/Editor/FirstPartyTools/ControlPlayMode/*, ControlPlayModeEditorStartup.cs
Adds bulk Play Mode view focus switching and wires startup, focus-change, update, and throttled reconciliation events.

Always-on editor ticking

Layer / File(s) Summary
Continuous tick pump and request processing
Packages/src/Editor/Infrastructure/Threading/*, Packages/src/Editor/Infrastructure/Api/JsonRpcRequestProcessor.cs
Removes scoped tick-pump control and its trailing-window state, starts a throttled always-on SignalTick loop, and removes request-scope wrapping from JSON-RPC processing.

Connection retry focus observability

Layer / File(s) Summary
Correlated focus and restore outcomes
cli/project-runner/internal/projectrunner/connection_retry.go, connection_retry_focus_log.go
Stores focus-attempt metadata and logs focus success/failure plus restore success/failure, skipped restore, and unavailable restore outcomes.
Logging and retry tests
cli/project-runner/internal/projectrunner/connection_retry_test.go
Adds JSON-lines log parsing and tests for correlated restore outcomes, missing restorers, skipped restores, and no-focus silence; clarifies permanent connection retry handling.

CLI skill guidance

Layer / File(s) Summary
Focus-window and launch usage documentation
.agents/skills/*, .claude/skills/*, Packages/src/Editor/CliOnlyTools~/FocusWindow/*, Packages/src/Editor/CliOnlyTools~/Launch/*
Removes busy-state focus claims and documents retry-first launch guidance, launch restrictions, and Unity foregrounding behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant FocusService
  participant PlayModeViews
  participant UserSettings
  Editor->>FocusService: focus state changes or periodic reconciliation
  FocusService->>PlayModeViews: suppress or restore view focus behavior
  PlayModeViews-->>FocusService: number of changed views
  FocusService->>UserSettings: set or clear suppression flag
Loading
sequenceDiagram
  participant RetryController
  participant UnityFocusAPI
  participant VibeLog
  RetryController->>UnityFocusAPI: focus Unity process
  UnityFocusAPI-->>RetryController: focus result and restorer
  RetryController->>VibeLog: log correlated focus outcome
  RetryController->>UnityFocusAPI: restore focus or skip restoration
  RetryController->>VibeLog: log correlated restore outcome
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main user-facing change: background Unity editors stop stealing focus while continuing to serve commands.
Description check ✅ Passed The description is directly related to the pull request and accurately covers the pump, focus suppression, logs, skill wording, and verification.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/always-on-autotick-pump

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 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 `@Packages/src/Editor/InternalAPIBridge/PlayModeViewFocusBridge.cs`:
- Around line 25-33: Restore only PlayMode views owned by suppression instead of
every PlayUnfocused view: update SetPlayUnfocusedViewsToPlayFocused and its
matching-view logic in
Packages/src/Editor/InternalAPIBridge/PlayModeViewFocusBridge.cs#L25-L33 to
accept the tracked ownership set. Persist that suppression-owned view state
across domain reloads/restarts in PlayModeFocusSuppressionService in
Packages/src/Editor/FirstPartyTools/ControlPlayMode/PlayModeFocusSuppressionService.cs#L70-L101,
and add a regression test covering a pre-existing user-unfocused view alongside
a suppressed focused view in
Assets/Tests/Editor/PlayModeFocusSuppressionServiceTests.cs#L212-L230.
🪄 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 Plus

Run ID: 0456a1f8-751a-4c40-80d0-46f3c0df6e54

📥 Commits

Reviewing files that changed from the base of the PR and between f63be49 and 0e40f51.

⛔ Files ignored due to path filters (6)
  • Assets/Tests/Editor/PlayModeFocusSuppressionServiceTests.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/ControlPlayMode/PlayModeFocusSuppressionConstants.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/ControlPlayMode/PlayModeFocusSuppressionService.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/ControlPlayMode/PlayModeFocusSuppressionStartup.cs.meta is excluded by none and included by none
  • Packages/src/Editor/FirstPartyTools/ControlPlayMode/UnityCLILoop.FirstPartyTools.ControlPlayMode.Editor.asmdef is excluded by none and included by none
  • Packages/src/Editor/InternalAPIBridge/PlayModeViewFocusBridge.cs.meta is excluded by none and included by none
📒 Files selected for processing (20)
  • .agents/skills/uloop-focus-window/SKILL.md
  • .agents/skills/uloop-launch/SKILL.md
  • .claude/skills/uloop-focus-window/SKILL.md
  • .claude/skills/uloop-launch/SKILL.md
  • Assets/Tests/Editor/AutoTickPumpControllerTests.cs
  • Assets/Tests/Editor/PlayModeFocusSuppressionServiceTests.cs
  • Packages/src/Editor/CliOnlyTools~/FocusWindow/Skill/SKILL.md
  • Packages/src/Editor/CliOnlyTools~/Launch/Skill/SKILL.md
  • Packages/src/Editor/FirstPartyTools/ControlPlayMode/ControlPlayModeEditorStartup.cs
  • Packages/src/Editor/FirstPartyTools/ControlPlayMode/PlayModeFocusSuppressionConstants.cs
  • Packages/src/Editor/FirstPartyTools/ControlPlayMode/PlayModeFocusSuppressionService.cs
  • Packages/src/Editor/FirstPartyTools/ControlPlayMode/PlayModeFocusSuppressionStartup.cs
  • Packages/src/Editor/Infrastructure/Api/JsonRpcRequestProcessor.cs
  • Packages/src/Editor/Infrastructure/Threading/AutoTickPumpConstants.cs
  • Packages/src/Editor/Infrastructure/Threading/AutoTickPumpController.cs
  • Packages/src/Editor/Infrastructure/Threading/AutoTickPumpService.cs
  • Packages/src/Editor/InternalAPIBridge/PlayModeViewFocusBridge.cs
  • cli/project-runner/internal/projectrunner/connection_retry.go
  • cli/project-runner/internal/projectrunner/connection_retry_focus_log.go
  • cli/project-runner/internal/projectrunner/connection_retry_test.go
💤 Files with no reviewable changes (5)
  • Packages/src/Editor/CliOnlyTools~/FocusWindow/Skill/SKILL.md
  • Assets/Tests/Editor/AutoTickPumpControllerTests.cs
  • .agents/skills/uloop-focus-window/SKILL.md
  • Packages/src/Editor/Infrastructure/Threading/AutoTickPumpController.cs
  • .claude/skills/uloop-focus-window/SKILL.md

Comment thread Packages/src/Editor/InternalAPIBridge/PlayModeViewFocusBridge.cs
@hatayama
hatayama merged commit 55d7c37 into v3-beta Jul 27, 2026
15 checks passed
@hatayama
hatayama deleted the feat/always-on-autotick-pump branch July 27, 2026 15:26
@github-actions github-actions Bot mentioned this pull request Jul 27, 2026
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