emberjs/ember.js · error · Error
Attempted to associate a destroyable child with an object th
Error message
Attempted to associate a destroyable child with an object that is already destroying or destroyed
What it means
associateDestroyableChild links a child destroyable's lifetime to a parent so the child is destroyed with the parent. In DEBUG builds, doing this on a parent that is already destroying/destroyed is invalid — the child could never be cleaned up through the parent — so it throws.
Source
Thrown at packages/@glimmer/destroyable/index.ts:134
DESTROYABLE_META.set(destroyable, meta as unknown as DestroyableMeta<Destroyable>);
}
return meta;
}
let meta = DESTROYABLE_META.get(destroyable);
if (meta === undefined) {
meta = createMeta(destroyable) as unknown as DestroyableMeta<Destroyable>;
DESTROYABLE_META.set(destroyable, meta);
}
return meta as unknown as DestroyableMeta<T>;
}
export function associateDestroyableChild<T extends Destroyable>(parent: Destroyable, child: T): T {
if (DEBUG && isDestroying(parent)) {
throw new Error(
'Attempted to associate a destroyable child with an object that is already destroying or destroyed'
);
}
let parentMeta = getDestroyableMeta(parent);
let childMeta = getDestroyableMeta(child);
parentMeta.children = push(parentMeta.children, child);
childMeta.parents = push(childMeta.parents, parent);
return child;
}
export function registerDestructor<T extends Destroyable>(
destroyable: T,
destructor: Destructor<T>,
eager = false
): Destructor<T> {View on GitHub (pinned to 26f97246a8)
Solutions
- Associate children at creation time, before any teardown can begin
- Check isDestroying(parent)/isDestroyed(parent) before associating and dispose the child independently otherwise
- Cancel async work on teardown (use a token or the parent's destructor) so late resources aren't attached
- Restructure so the child is owned by a longer-lived, still-active parent
Example fix
// before
parent.resources.push(acquire());
associateDestroyableChild(parent, child);
// after
if (isDestroying(parent) || isDestroyed(parent)) {
destroy(child);
} else {
associateDestroyableChild(parent, child);
} Defensive patterns
Strategy: validation
Validate before calling
import { isDestroying, isDestroyed } from '@glimmer/destroyable';
if (isDestroying(parent) || isDestroyed(parent)) {
destroy(child); // dispose independently
} else {
associateDestroyableChild(parent, child);
} Type guard
function canAssociate(parent) { return !isDestroying(parent) && !isDestroyed(parent); } Try / catch
try {
associateDestroyableChild(parent, child);
} catch (e) {
if (String(e.message).includes('associate a destroyable child')) {
destroy(child); // parent is gone; clean up child directly
} else { throw e; }
} Prevention
- Associate children at creation time, never in async callbacks after teardown may have started
- Cancel async operations during destroy so late resources aren't created
- Check isDestroying/isDestroyed before any association call
When it happens
Trigger: Calling associateDestroyableChild(parent, child) after destroy(parent) started; creating resources inside willDestroy; async work resolving after teardown began and then associating its resources with the dead parent.
Common situations: Promise callbacks attaching resources post-teardown; renderComponent/helpers creating destroyables during shutdown; tests with leaked async teardown ordering.
Related errors
- Attempted to register a destructor with an object that is al
- Attempted to unregister a destructor with an object that is
- deprecation override for ${id} not found
- You must pass both the owner and args to super() in your com
- You must pass both the owner and args to super() in your com
AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01).
Data as JSON: /api/errors/1f346deb446ac8a9.
Report an issue: GitHub.