Quick answer: Always call GodotObject.IsInstanceValid(node) before touching a long-lived reference. Disconnect signals in _ExitTree. Null out cached references when the source node frees.

Two scenes share a manager. Scene A frees a child while Scene B holds a reference to it. Scene B’s next callback throws “ObjectDisposedException: Cannot access a disposed object.” The native object is gone; the C# wrapper noticed.

The Symptom

ObjectDisposedException thrown from C# code that touches a Godot Node. The exception happens at unpredictable times because the underlying free was triggered elsewhere.

What Causes This

Godot manages objects natively. C# wraps them through a marshalled handle. QueueFree on a node destroys the native object at the next idle. Any C# reference that survives is a wrapper around a now-destroyed native object — access throws.

The Fix

Pattern 1: Validity check before use.

if (GodotObject.IsInstanceValid(_target))
{
    _target.Position = newPos;
}
else
{
    _target = null;
}

Pattern 2: Subscribe to TreeExited.

public override void _Ready()
{
    _target = GetNode<Node2D>("Player");
    _target.TreeExited += () => _target = null;
}

The TreeExited signal fires before the dispose. Null out your reference immediately so subsequent code never touches the dead wrapper.

Pattern 3: Disconnect in _ExitTree.

public override void _ExitTree()
{
    if (GodotObject.IsInstanceValid(_publisher))
        _publisher.MySignal -= Handler;
}

Long-lived publishers shouldn’t fire callbacks into disposed listeners.

Async / Await Pitfall

Awaits resume on the SynchronizationContext. The node may be disposed by the time the continuation runs:

public async Task DoWork()
{
    await Task.Delay(2000);
    if (!GodotObject.IsInstanceValid(this)) return;   // guard

    Position += Vector2.Right;
}

Or use UniTask’s WithCancellation tied to a TreeExited token (third-party packages bring this to Godot).

Verifying

Reproduce the free-then-touch scenario. Without IsInstanceValid: throws. With: silent return. Add Debug.Print to confirm the guard fires.

“IsInstanceValid before touch. Disconnect in _ExitTree. Null on TreeExited. No more disposed exceptions.”

Related Issues

For C# trim issues, see trim removed. For signal listener missing, see signal listener.

Validate. Null. Disconnect. Wrappers stay safe.