moeru-ai/airi · info
[Stage] Failed to post caption reset for ${source} (channel
Error message
[Stage] Failed to post caption reset for ${source} (channel may be closed) What it means
Stage.vue's resetAssistantSpeechSurface resets speech state (nowSpeaking, mouthOpenSize, assistantCaption) and then posts an empty 'caption-assistant' event over the stage channel (postCaption). If the underlying channel is already closed when the post runs, it throws synchronously and this warning is logged. The local state reset happens first, so the UI is still cleared; only the cross-surface caption broadcast was lost.
Source
Thrown at packages/stage-ui/src/components/scenes/Stage.vue:191
}, 100)
}))
const audioAnalyser = ref<AnalyserNode>()
const lipSyncStarted = ref(false)
const lipSyncLoopId = ref<number>()
const live2dLipSync = ref<Live2DLipSync>()
const live2dLipSyncOptions: Live2DLipSyncOptions = { mouthUpdateIntervalMs: 50, mouthLerpWindowMs: 50 }
function resetAssistantSpeechSurface(source: string) {
nowSpeaking.value = false
mouthOpenSize.value = 0
assistantCaption.value = ''
try {
postCaption({ type: 'caption-assistant', text: '' })
}
catch (error) {
console.warn(`[Stage] Failed to post caption reset for ${source} (channel may be closed)`, { error })
}
try {
postPresent({ type: 'assistant-reset' })
}
catch (error) {
console.warn(`[Stage] Failed to post present reset for ${source} (channel may be closed)`, { error })
}
}
const { activeCard } = storeToRefs(useAiriCardStore())
const speechStore = useSpeechStore()
const { ssmlEnabled, activeSpeechProvider, activeSpeechModel, activeSpeechVoice, pitch } = storeToRefs(speechStore)
const activeCardId = computed(() => activeCard.value?.name ?? 'default')
const speechRuntimeStore = useSpeechRuntimeStore()
const { trackOfficialTtsAutoEnabled } = useAnalytics()
let officialAutoTtsTrackedForTurn = false
const backgroundStore = useBackgroundStore()View on GitHub (pinned to 677329427f)
Solutions
- Treat as benign when it only appears during teardown or reload — state reset already succeeded
- Stop speech and post the caption reset before closing/disposing the stage channel (order teardown: speech stop -> caption reset -> channel close)
- Track channel closed state and skip posting when already closed to silence the warning
Example fix
// before
try {
postCaption({ type: 'caption-assistant', text: '' })
}
catch (error) {
console.warn(`[Stage] Failed to post caption reset for ${source} (channel may be closed)`, { error })
}
// after
if (!captionChannelClosed.value) {
try {
postCaption({ type: 'caption-assistant', text: '' })
}
catch (error) {
captionChannelClosed.value = true
console.warn(`[Stage] Failed to post caption reset for ${source} (channel may be closed)`, { error })
}
} Defensive patterns
Strategy: validation
Validate before calling
// track channel liveness once
let captionChannelClosed = false
captionChannel.onclose = () => { captionChannelClosed = true }
// before posting
if (!captionChannelClosed) {
postCaption({ type: 'caption-assistant', text: '' })
} Try / catch
try {
postCaption({ type: 'caption-assistant', text: '' })
}
catch (error) {
// channel closed during teardown: state already reset, safe to ignore
if (!isTeardown)
console.warn('[Stage] caption reset failed', { error })
} Prevention
- Order teardown: stop speech -> post resets -> close channels
- Post caption resets from the channel owner so posts cannot outlive the channel
- Treat this exact message during unload/reload as noise, not a defect
When it happens
Trigger: Calling resetAssistantSpeechSurface (speech end, abort, or teardown paths) after the caption channel closed: component unmount, window teardown, hot reload, or a second reset racing the channel's close.
Common situations: Closing the desktop window mid-utterance; Vue HMR re-running setup; duplicated cleanup invocations (error handler plus onUnmounted both firing).
Related errors
- [Stage] Failed to post present reset for ${source} (channel
- [Speech Pipeline] provider/voice/model changed mid-session,
- MiniMax TTS request failed: ${response.status} ${response.st
- OpenRouter audio request failed: ${response.status} ${await
- Invalid speech request body
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/02da5bac2d16f4ec.
Report an issue: GitHub.