Skip to content

Fix editor state always reporting stale when Unity is backgrounded#789

Merged
dsarno merged 3 commits intoCoplayDev:betafrom
dsarno:fix/editor-state-stale-when-backgrounded
Feb 20, 2026
Merged

Fix editor state always reporting stale when Unity is backgrounded#789
dsarno merged 3 commits intoCoplayDev:betafrom
dsarno:fix/editor-state-stale-when-backgrounded

Conversation

@dsarno
Copy link
Collaborator

@dsarno dsarno commented Feb 20, 2026

Summary

Fixes editor_state resource always reporting ready_for_tools: false and is_stale: true when Unity is backgrounded (not focused), even though Unity is idle and fully responsive.

Root Cause

EditorStateCache.GetSnapshot() returns a cached clone stamped with observed_at_unix_ms from the last OnUpdate tick. When Unity is backgrounded, EditorApplication.update is 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" to blocking_reasons and sets ready_for_tools: false.

This affects every client using mcpforunity://editor/state on 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(), so observed_at_unix_ms reflects when the snapshot was served, not when the update loop last ran.

Before: age_ms: 37408, is_stale: true, ready_for_tools: false
After: age_ms: 121, is_stale: false, ready_for_tools: true

Test plan

  • Verified on HTTP with Unity backgrounded: is_stale: false, ready_for_tools: true, age_ms < 200ms
  • Compilation clean, no console errors
  • No behavioral change when Unity is focused (OnUpdate runs normally, timestamps are already fresh)

🤖 Generated with Claude Code

Summary by Sourcery

Bug Fixes:

  • Ensure editor_state reports fresh status and ready_for_tools when Unity is backgrounded by stamping snapshots with the current time instead of the last update tick.

Summary by CodeRabbit

  • Improvements

    • Snapshot state handling now stamps timestamps when the app is backgrounded to reduce staleness in background scenarios.
    • Upstream package option now targets the main branch reference more explicitly for clearer upstream resolution.
  • Documentation

    • Added guidance for switching the Unity package source across projects, including discovery and manifest update steps.

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>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 20, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Updates 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 check

sequenceDiagram
    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
Loading

Class diagram for updated EditorStateCache GetSnapshot behavior

classDiagram
    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
Loading

File-Level Changes

Change Details Files
Ensure editor_state snapshots use a fresh observed_at_unix_ms based on serve time instead of last update tick to avoid false staleness when Unity is backgrounded.
  • Keep returning a deep-cloned JObject from the cached editor state to avoid mutation bugs.
  • After cloning the cached state, set the observed_at_unix_ms field to the current UTC Unix time in milliseconds using DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().
  • Return the timestamp-adjusted clone so server-side age_ms calculations reflect snapshot serve time rather than the last EditorApplication.update tick.
MCPForUnity/Editor/Services/EditorStateCache.cs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

GetSnapshot continues to return a clone of the cached snapshot; when the Unity application is backgrounded (not active), the clone is stamped with observed_at_unix_ms (current UTC Unix ms) before being returned, otherwise the prior behavior is preserved.

Changes

Cohort / File(s) Summary
Snapshot Timestamp Augmentation
MCPForUnity/Editor/Services/EditorStateCache.cs
GetSnapshot now conditionally sets observed_at_unix_ms on the cloned snapshot when Unity is backgrounded; clone behavior otherwise unchanged.
Skill doc addition
.claude/skills/mcp-source/SKILL.md
New skill documentation added describing tools, args, repo detection, manifest discovery, URL construction (main/beta/branch/local), manifest editing, and reporting.
Upstream URL tweak
mcp_source.py
Appended #main fragment to the upstream main URL in build_options, changing the explicit upstream target for the "[1] Upstream main" option.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

Poem

🐰 I hopped through code with nimble feet so light,
I stamp the moment when snapshots take flight.
In background hush I mark the time they see,
A tiny hop of milliseconds — that's me! 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the root cause, fix, and testing. However, it does not follow the required template structure with sections like 'Type of Change', 'Changes Made', 'Documentation Updates', and 'Related Issues'. Restructure the description to match the repository template with explicit sections: Type of Change (Bug fix), Changes Made, Testing/Screenshots, Documentation Updates checklist, and Related Issues.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix editor state always reporting stale when Unity is backgrounded' directly matches the main change: addressing stale state reporting when Unity loses focus.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 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.

dsarno and others added 2 commits February 19, 2026 20:28
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>
@dsarno dsarno merged commit c43efe3 into CoplayDev:beta Feb 20, 2026
1 of 2 checks passed
msanatan pushed a commit to msanatan/unity-mcp that referenced this pull request Feb 25, 2026
…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>
msanatan pushed a commit to msanatan/unity-mcp that referenced this pull request Feb 25, 2026
…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>
@coderabbitai coderabbitai bot mentioned this pull request Mar 4, 2026
4 tasks
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