moeru-ai/airi · warning

Error removing old model:

Error message

Error removing old model:

What it means

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.

Source

Thrown at packages/stage-ui-live2d/src/components/scenes/live2d/Model.vue:235

    catch {
      modelLoading.value = false
      componentState.value = 'mounted'
      return
    }
  }

  // REVIEW: here as await until(...) guarded the pixiApp and stage to be valid.
  if (model.value && pixiApp.value?.stage) {
    // Dispose expression controller before destroying the old model
    expressionController.dispose()
    internalModelRef.value = undefined

    try {
      pixiApp.value.stage.removeChild(model.value)
      model.value.destroy()
    }
    catch (error) {
      console.warn('Error removing old model:', error)
    }
    model.value = undefined
  }
  if (!modelSrcRef.value) {
    console.warn('No Live2D model source provided.')
    modelLoading.value = false
    componentState.value = 'mounted'
    return
  }

  try {
    if (isUnmounted) {
      modelLoading.value = false
      componentState.value = 'mounted'
      return
    }

    const live2DModel = new Live2DModel<PixiLive2DInternalModel>()

View on GitHub (pinned to 677329427f)

Solutions

  1. Proceed — the new model load continues after the warning
  2. Serialize swaps behind a mutex like loadModel's modelLoadMutex to prevent overlapping destroys
  3. Keep the source guard: only dispose when model.value and pixiApp.value?.stage both exist
  4. After a context loss, recreate the PIXI app instead of reusing a half-destroyed stage
Defensive patterns

Strategy: try-catch

Validate before calling

if (!model.value || !pixiApp.value?.stage) return // skip dispose when the app was torn down (source guard)

Try / catch

try {
  pixiApp.value.stage.removeChild(model.value)
  model.value.destroy()
}
catch (error) {
  console.warn('Error removing old model:', error)
}
model.value = undefined // proceed with the new model regardless (source pattern)

Prevention

When it happens

Trigger: Switching models rapidly so a previous destroy overlaps the next load; WebGL context lost before destroy; destroying a model whose textures were already freed.

Common situations: User rapidly switches between Live2D models in settings; device suspends and resumes with a lost context; HMR reload during a model swap.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/c5b654a9571790ec. Report an issue: GitHub.