{"record":{"id":"c5b654a9571790ec","repo":"moeru-ai/airi","slug":"error-removing-old-model","errorCode":null,"errorMessage":"Error removing old model:","messagePattern":"Error removing old model:","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui-live2d/src/components/scenes/live2d/Model.vue","lineNumber":235,"sourceCode":"    catch {\n      modelLoading.value = false\n      componentState.value = 'mounted'\n      return\n    }\n  }\n\n  // REVIEW: here as await until(...) guarded the pixiApp and stage to be valid.\n  if (model.value && pixiApp.value?.stage) {\n    // Dispose expression controller before destroying the old model\n    expressionController.dispose()\n    internalModelRef.value = undefined\n\n    try {\n      pixiApp.value.stage.removeChild(model.value)\n      model.value.destroy()\n    }\n    catch (error) {\n      console.warn('Error removing old model:', error)\n    }\n    model.value = undefined\n  }\n  if (!modelSrcRef.value) {\n    console.warn('No Live2D model source provided.')\n    modelLoading.value = false\n    componentState.value = 'mounted'\n    return\n  }\n\n  try {\n    if (isUnmounted) {\n      modelLoading.value = false\n      componentState.value = 'mounted'\n      return\n    }\n\n    const live2DModel = new Live2DModel<PixiLive2DInternalModel>()","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui-live2d/src/components/scenes/live2d/Model.vue#L217-L253","documentation":"While swapping Live2D models, removing the old model from the PIXI stage and destroying it threw. The expression controller was already disposed and internalModelRef cleared; after the catch, model.value is undefined and loading of the new model proceeds. If removeChild failed before destroy, stale display objects may linger on the stage.","triggerScenarios":"Switching models rapidly so a previous destroy overlaps the next load; WebGL context lost before destroy; destroying a model whose textures were already freed.","commonSituations":"User rapidly switches between Live2D models in settings; device suspends and resumes with a lost context; HMR reload during a model swap.","solutions":["Proceed — the new model load continues after the warning","Serialize swaps behind a mutex like loadModel's modelLoadMutex to prevent overlapping destroys","Keep the source guard: only dispose when model.value and pixiApp.value?.stage both exist","After a context loss, recreate the PIXI app instead of reusing a half-destroyed stage"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!model.value || !pixiApp.value?.stage) return // skip dispose when the app was torn down (source guard)","typeGuard":null,"tryCatchPattern":"try {\n  pixiApp.value.stage.removeChild(model.value)\n  model.value.destroy()\n}\ncatch (error) {\n  console.warn('Error removing old model:', error)\n}\nmodel.value = undefined // proceed with the new model regardless (source pattern)","preventionTips":["Serialize model swaps behind a mutex like loadModel's modelLoadMutex to avoid racing destroys","Dispose the expression controller before destroying the model (source ordering)","After a WebGL context loss, recreate the PIXI app instead of reusing a half-destroyed stage"],"tags":["live2d","pixi","destroy","model-swap"],"backgroundTag":"live2d-model-dispose-failed","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}