Quick answer: Guard cached node references with GodotObject.IsInstanceValid(node) before use. Better: null the reference when the node leaves the tree.

An AI caches its target node. The target gets QueueFree’d. Next frame, accessing target.GlobalPosition throws ObjectDisposedException — the C# wrapper outlived the engine object.

Validity Check

if (GodotObject.IsInstanceValid(_target))
{
    var dir = (_target.GlobalPosition - GlobalPosition).Normalized();
}
else
{
    _target = null;   // drop the dead reference
    PickNewTarget();
}

IsInstanceValid returns false once the underlying engine object is freed. Always check before touching a cached reference that something else might free.

Null on Tree Exit

public void SetTarget(Node3D target)
{
    _target = target;
    target.TreeExited += () => { if (_target == target) _target = null; };
}

Subscribe to TreeExited so the reference is cleared the moment the node leaves — no stale pointer to check later.

QueueFree Is Deferred

QueueFree doesn’t free immediately — it frees at end of frame. The node is still “valid” this frame but its IsQueuedForDeletion() returns true. Check that too if you must act this frame.

Don't Hold Strong Refs Long-Term

For peripheral references (AI target, lock-on), prefer storing the node’s path or instance ID and resolving on demand — or accept that you must IsInstanceValid-guard every access.

Verifying

Free a targeted node mid-combat. The AI drops the target gracefully and re-acquires. No ObjectDisposedException across many spawn/kill cycles.

“The C# wrapper can outlive the engine object. IsInstanceValid before use, or null on TreeExited.”

Build a TargetRef<T> helper struct that wraps a node + auto-validity check — centralizes the guard so you never forget it.