{"record":{"id":"1f346deb446ac8a9","repo":"emberjs/ember.js","slug":"attempted-to-associate-a-destroyable-child-with-an","errorCode":null,"errorMessage":"Attempted to associate a destroyable child with an object that is already destroying or destroyed","messagePattern":"Attempted to associate a destroyable child with an object that is already destroying or destroyed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@glimmer/destroyable/index.ts","lineNumber":134,"sourceCode":"      DESTROYABLE_META.set(destroyable, meta as unknown as DestroyableMeta<Destroyable>);\n    }\n\n    return meta;\n  }\n\n  let meta = DESTROYABLE_META.get(destroyable);\n\n  if (meta === undefined) {\n    meta = createMeta(destroyable) as unknown as DestroyableMeta<Destroyable>;\n    DESTROYABLE_META.set(destroyable, meta);\n  }\n\n  return meta as unknown as DestroyableMeta<T>;\n}\n\nexport function associateDestroyableChild<T extends Destroyable>(parent: Destroyable, child: T): T {\n  if (DEBUG && isDestroying(parent)) {\n    throw new Error(\n      'Attempted to associate a destroyable child with an object that is already destroying or destroyed'\n    );\n  }\n\n  let parentMeta = getDestroyableMeta(parent);\n  let childMeta = getDestroyableMeta(child);\n\n  parentMeta.children = push(parentMeta.children, child);\n  childMeta.parents = push(childMeta.parents, parent);\n\n  return child;\n}\n\nexport function registerDestructor<T extends Destroyable>(\n  destroyable: T,\n  destructor: Destructor<T>,\n  eager = false\n): Destructor<T> {","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/emberjs/ember.js/blob/26f97246a8bf2e28edf26ac3093da2e86c04ffc5/packages/@glimmer/destroyable/index.ts#L116-L152","documentation":"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.","triggerScenarios":"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.","commonSituations":"Promise callbacks attaching resources post-teardown; renderComponent/helpers creating destroyables during shutdown; tests with leaked async teardown ordering.","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"],"exampleFix":"// before\nparent.resources.push(acquire());\nassociateDestroyableChild(parent, child);\n// after\nif (isDestroying(parent) || isDestroyed(parent)) {\n  destroy(child);\n} else {\n  associateDestroyableChild(parent, child);\n}","handlingStrategy":"validation","validationCode":"import { isDestroying, isDestroyed } from '@glimmer/destroyable';\nif (isDestroying(parent) || isDestroyed(parent)) {\n  destroy(child); // dispose independently\n} else {\n  associateDestroyableChild(parent, child);\n}","typeGuard":"function canAssociate(parent) { return !isDestroying(parent) && !isDestroyed(parent); }","tryCatchPattern":"try {\n  associateDestroyableChild(parent, child);\n} catch (e) {\n  if (String(e.message).includes('associate a destroyable child')) {\n    destroy(child); // parent is gone; clean up child directly\n  } else { throw e; }\n}","preventionTips":["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"],"tags":["lifecycle","destroyable","glimmer","debug","teardown"],"backgroundTag":"associate-destroyable-after-destroy","analyzedSha":"26f97246a8bf2e28edf26ac3093da2e86c04ffc5","analyzedAt":"2026-09-01T05:01:28.182Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}