stride3d/stride · error · InvalidOperationException

This TransformComponent's parent is not the expected value.

Error message

This TransformComponent's parent is not the expected value.

What it means

Thrown by TransformComponent.Children's OnTransformRemoved hook when removing a transform whose Parent does not match the transform that owns the collection. Mirrors the add-side check: the collection only removes transforms it actually parents. Hitting it means the child was reparented or its parent pointer changed out from under the collection.

Solutions

  1. Guard with if (item.Parent == transform) before calling Remove; skip or log otherwise.
  2. Remove the child from the parent that currently owns it (query item.Parent.Children.Remove(item)).
  3. Avoid duplicated detach logic across code paths; centralize re-parenting in one helper that detaches once.
  4. After a failed hierarchy operation, re-read Parent before retrying removal rather than assuming the old structure.

Example fix

// before
parent.Transform.Children.Remove(child.Transform); // throws if child.Parent != parent
// after
if (child.Transform.Parent == parent.Transform)
    parent.Transform.Children.Remove(child.Transform);
Defensive patterns

Strategy: validation

Validate before calling

bool canDetach(TransformComponent parent, TransformComponent child) => child.Parent == parent;

Type guard

bool IsOwnedBy(TransformComponent child, TransformComponent parent) => ReferenceEquals(child?.Parent, parent);

Try / catch

try { parentTransform.Children.Remove(childTransform); } catch (InvalidOperationException ex) { log.Warn($"Child parent mismatch: {ex.Message}"); }

Prevention

When it happens

Trigger: Calling transform.Children.Remove(item) / Clear / SetItem when item.Parent != transform — e.g. item was already detached and re-added elsewhere, or item.parent was nulled by a prior remove that threw mid-way.

Common situations: Double-detach in cleanup code (removing a child twice or from two different parents); scripts that set parent via a different collection then call Remove on the stale one; exception recovery paths leaving half-detached hierarchies.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/a9d2a1206e6f233d. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Engine/Engine/TransformComponent.cs:367

            public TransformChildrenCollection(TransformComponent transformParam)
            {
                transform = transformParam;
            }

            private void OnTransformAdded(TransformComponent item)
            {
                if (item.Parent != null)
                    throw new InvalidOperationException("This TransformComponent already has a Parent, detach it first.");

                item.parent = transform;

                Entity?.EntityManager?.OnHierarchyChanged(item.Entity);
                Entity?.EntityManager?.GetProcessor<TransformProcessor>().NotifyChildrenCollectionChanged(item, true);
            }
            private void OnTransformRemoved(TransformComponent item)
            {
                if (item.Parent != transform)
                    throw new InvalidOperationException("This TransformComponent's parent is not the expected value.");

                item.parent = null;

                Entity?.EntityManager?.OnHierarchyChanged(item.Entity);
                Entity?.EntityManager?.GetProcessor<TransformProcessor>().NotifyChildrenCollectionChanged(item, false);
            }

            /// <inheritdoc/>
            protected override void InsertItem(int index, TransformComponent item)
            {
                base.InsertItem(index, item);
                OnTransformAdded(item);
            }

            /// <inheritdoc/>
            protected override void RemoveItem(int index)
            {
                OnTransformRemoved(this[index]);

View on GitHub (pinned to 96fad776d2)