Fix editor state always reporting stale when Unity is backgrounded#789
Conversation
EditorStateCache.GetSnapshot() returned a cached clone with the observed_at_unix_ms from the last OnUpdate tick. When Unity is backgrounded, OnUpdate is throttled, so the timestamp grows stale even though the data is current and Unity is responsive. Now stamps the clone at serve time so the server-side staleness check (>2s = stale) reflects when the snapshot was served, not when the update loop last ran. This fixes ready_for_tools being false for every backgrounded Unity editor on HTTP. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reviewer's guide (collapsed on small PRs)Reviewer's GuideUpdates EditorStateCache.GetSnapshot() to re-stamp the cached editor state snapshot with the current timestamp at serve time so backgrounded Unity editors no longer appear perpetually stale to MCP clients, while preserving the cached data and clone behavior. Sequence diagram for updated editor state snapshot serving and staleness checksequenceDiagram
actor Developer
participant MCPClient
participant HTTPServer
participant UnityEditor
participant EditorStateCache
participant StalenessChecker
Developer->>MCPClient: Request editor_state
MCPClient->>HTTPServer: GET /editor/state
HTTPServer->>UnityEditor: Request editor state snapshot
UnityEditor->>EditorStateCache: GetSnapshot()
activate EditorStateCache
EditorStateCache->>EditorStateCache: DeepClone(_cached) into clone
EditorStateCache->>EditorStateCache: Set clone.observed_at_unix_ms = UtcNow()
EditorStateCache-->>UnityEditor: return clone
deactivate EditorStateCache
UnityEditor-->>HTTPServer: editor_state snapshot (fresh observed_at_unix_ms)
HTTPServer->>StalenessChecker: Compute age_ms = now - observed_at_unix_ms
StalenessChecker-->>HTTPServer: is_stale = age_ms > 2000 ? true : false
HTTPServer-->>MCPClient: editor_state with is_stale and ready_for_tools
MCPClient-->>Developer: Show responsive, non-stale editor state
Class diagram for updated EditorStateCache GetSnapshot behaviorclassDiagram
class EditorStateCache {
- static JObject _cached
- static object _cacheLock
+ static JObject GetSnapshot()
}
class JObject {
+ JObject DeepClone()
+ object Item[string index]
}
EditorStateCache ..> JObject : uses
%% GetSnapshot behavior (conceptual steps)
class GetSnapshotBehavior {
+ DeepCloneCachedState()
+ StampObservedAtUnixMs()
+ ReturnClone()
}
EditorStateCache ..> GetSnapshotBehavior : implemented by
%% Method semantics:
%% DeepCloneCachedState: clone = (JObject)_cached.DeepClone()
%% StampObservedAtUnixMs: clone[observed_at_unix_ms] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
%% ReturnClone: return clone
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughGetSnapshot continues to return a clone of the cached snapshot; when the Unity application is backgrounded (not active), the clone is stamped with Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@MCPForUnity/Editor/Services/EditorStateCache.cs`:
- Around line 517-525: The code always overwrites observed_at_unix_ms on the
cloned snapshot which masks staleness and breaks coherence with
activity.since_unix_ms; change GetSnapshot so it only resets
clone["observed_at_unix_ms"] when the Editor is backgrounded (e.g., check
EditorApplication.isFocused or Application.isFocused and only re-stamp when not
focused), and when you do re-stamp also update the clone's
activity.since_unix_ms (the field derived from _observedUnixMs) to a matching
value to avoid creating a huge observed_at_unix_ms − activity.since_unix_ms gap;
reference _cached, _observedUnixMs, observed_at_unix_ms and
activity.since_unix_ms in the EditorStateCache/GetSnapshot logic.
Addresses CodeRabbit review feedback: the unconditional re-stamp defeated the staleness check for genuinely unresponsive focused editors. Now uses InternalEditorUtility.isApplicationActive to conditionally re-stamp only when Unity is backgrounded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Claude Code skill for switching MCP package source in Unity projects. Fix mcp_source.py upstream main URL to include required #main branch suffix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…oplayDev#789) * Fix editor state always reporting stale when Unity is backgrounded EditorStateCache.GetSnapshot() returned a cached clone with the observed_at_unix_ms from the last OnUpdate tick. When Unity is backgrounded, OnUpdate is throttled, so the timestamp grows stale even though the data is current and Unity is responsive. Now stamps the clone at serve time so the server-side staleness check (>2s = stale) reflects when the snapshot was served, not when the update loop last ran. This fixes ready_for_tools being false for every backgrounded Unity editor on HTTP. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Only re-stamp editor state when Unity is backgrounded Addresses CodeRabbit review feedback: the unconditional re-stamp defeated the staleness check for genuinely unresponsive focused editors. Now uses InternalEditorUtility.isApplicationActive to conditionally re-stamp only when Unity is backgrounded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add /mcp-source skill and fix upstream main URL Add Claude Code skill for switching MCP package source in Unity projects. Fix mcp_source.py upstream main URL to include required #main branch suffix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…oplayDev#789) * Fix editor state always reporting stale when Unity is backgrounded EditorStateCache.GetSnapshot() returned a cached clone with the observed_at_unix_ms from the last OnUpdate tick. When Unity is backgrounded, OnUpdate is throttled, so the timestamp grows stale even though the data is current and Unity is responsive. Now stamps the clone at serve time so the server-side staleness check (>2s = stale) reflects when the snapshot was served, not when the update loop last ran. This fixes ready_for_tools being false for every backgrounded Unity editor on HTTP. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Only re-stamp editor state when Unity is backgrounded Addresses CodeRabbit review feedback: the unconditional re-stamp defeated the staleness check for genuinely unresponsive focused editors. Now uses InternalEditorUtility.isApplicationActive to conditionally re-stamp only when Unity is backgrounded. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add /mcp-source skill and fix upstream main URL Add Claude Code skill for switching MCP package source in Unity projects. Fix mcp_source.py upstream main URL to include required #main branch suffix. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Fixes
editor_stateresource always reportingready_for_tools: falseandis_stale: truewhen Unity is backgrounded (not focused), even though Unity is idle and fully responsive.Root Cause
EditorStateCache.GetSnapshot()returns a cached clone stamped withobserved_at_unix_msfrom the lastOnUpdatetick. When Unity is backgrounded,EditorApplication.updateis throttled by Unity, so the timestamp grows stale. The server-side staleness check (age_ms > 2000) then marks the state as stale, which adds"stale_status"toblocking_reasonsand setsready_for_tools: false.This affects every client using
mcpforunity://editor/stateon HTTP transport with a backgrounded Unity editor — which is the normal workflow when using Claude Code or other terminal-based MCP clients.Fix
Stamp the cloned snapshot with the current time at serve time in
GetSnapshot(), soobserved_at_unix_msreflects when the snapshot was served, not when the update loop last ran.Before:
age_ms: 37408, is_stale: true, ready_for_tools: falseAfter:
age_ms: 121, is_stale: false, ready_for_tools: trueTest plan
is_stale: false,ready_for_tools: true,age_ms< 200ms🤖 Generated with Claude Code
Summary by Sourcery
Bug Fixes:
Summary by CodeRabbit
Improvements
Documentation