moeru-ai/airi · warning

[Spine] Failed to dispose SpineCanvas:

Error message

[Spine] Failed to dispose SpineCanvas:

What it means

disposeSpine() wraps spineCanvas.dispose() in try/catch; the dispose threw — commonly after WebGL context loss or when the update loop still touches resources. After the catch, every reference (assetCleanup, skeleton, animationState, bounds) is cleared and the store's isModelLoaded resets, so component state stays consistent though GPU resources may leak.

Source

Thrown at packages/stage-ui-spine/src/components/scenes/spine/Model.vue:101

// Mutable defaults handed to the animation manager. The manager reads these
// fields on every call, so mutating them in place (see the prop watches
// below) propagates live setting changes without rebuilding the manager.
const animationDefaults = {
  mixDuration: props.defaultMixDuration,
  idleAnimationEnabled: props.idleAnimationEnabled,
}

const canvas = toRef(() => props.canvas)
const modelSrc = toRef(() => props.modelSrc)
const paused = toRef(() => props.paused)

function disposeSpine() {
  if (spineCanvas) {
    try {
      spineCanvas.dispose()
    }
    catch (err) {
      console.warn('[Spine] Failed to dispose SpineCanvas:', err)
    }
    spineCanvas = undefined
  }
  assetCleanup?.()
  assetCleanup = undefined
  animationManager = undefined
  skeleton = undefined
  animationState = undefined
  modelIntrinsicBounds = undefined
  spineStore.isModelLoaded = false
}

async function loadModel() {
  await modelLoadMutex.acquire()

  modelLoading.value = true
  componentState.value = 'loading'

View on GitHub (pinned to 677329427f)

Solutions

  1. Accept the warning if the app continues normally — state refs are reset regardless
  2. Stop the render/update loop before disposing the canvas
  3. Handle webglcontextlost early; dispose-after-loss commonly throws
  4. Keep the spine runtime version in sync with the asset exporter version
Defensive patterns

Strategy: try-catch

Validate before calling

if (!spineCanvas) return // source guard in disposeSpine

Try / catch

try {
  spineCanvas.dispose()
}
catch (err) {
  console.warn('[Spine] Failed to dispose SpineCanvas:', err)
}
// always reset refs and store flags afterwards (source pattern) so state stays consistent

Prevention

When it happens

Trigger: Unmounting or disposing while the render loop still updates the skeleton; WebGL context already lost; spine runtime version mismatch with exported assets.

Common situations: Scene switching during unmount; tab suspended then resumed with a lost context; spine runtime upgraded without re-exporting assets.

Related errors


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