moeru-ai/airi · warning
[parseActEmotion] Failed to parse ACT payload JSON: "${paylo
Error message
[parseActEmotion] Failed to parse ACT payload JSON: "${payloadText}" What it means
parseActEmotion() extracts a JSON object from a <|ACT {...}|> tag in an LLM message and JSON.parse's it inside a try/catch. When the captured group is not valid JSON (the catch path), it warns with the offending payloadText and returns { ok: false, emotion: null }, so the message simply carries no emotion — the queue handler skips the emotion emit. Malformed-but-parseable payloads are handled by the shape checks, not this warn.
Source
Thrown at packages/stage-ui/src/composables/queues.ts:49
const payload = JSON.parse(payloadText) as { emotion?: unknown }
const emotion = payload?.emotion
if (typeof emotion === 'string') {
const normalized = normalizeEmotionName(emotion)
if (normalized)
return { ok: true, emotion: { name: normalized, intensity: 1 } }
}
else if (emotion && typeof emotion === 'object' && !Array.isArray(emotion)) {
if ('name' in emotion && typeof (emotion as { name?: unknown }).name === 'string') {
const normalized = normalizeEmotionName((emotion as { name: string }).name)
if (normalized) {
const intensity = normalizeIntensity((emotion as { intensity?: unknown }).intensity)
return { ok: true, emotion: { name: normalized, intensity } }
}
}
}
}
catch (e) {
console.warn(`[parseActEmotion] Failed to parse ACT payload JSON: "${payloadText}"`, e)
}
return { ok: false, emotion: null as EmotionPayload | null }
}
return createQueue<string>({
handlers: [
async (ctx) => {
const actParsed = parseActEmotion(ctx.data)
if (actParsed.ok && actParsed.emotion) {
ctx.emit('emotion', actParsed.emotion)
emotionsQueue.enqueue(actParsed.emotion)
}
},
],
})
}
View on GitHub (pinned to 677329427f)
Solutions
- Log payloadText (already included in the warn) and identify the exact malformation.
- Tighten the system prompt / few-shot for the ACT tag format so JSON is strict (double quotes, no comments).
- Pre-sanitize the captured group (strip trailing commas, quotes normalization) or retry parse of the largest balanced {...} substring before giving up.
- Accept the degradation: ok:false already means 'no emotion for this message'.
Example fix
// before
const payload = JSON.parse(payloadText) // throws on {name: 'happy'}
// after
const payload = JSON.parse(payloadText.replace(/'/g, '"').replace(/(\w+)\s*:/g, '"$1":')) Defensive patterns
Strategy: validation
Validate before calling
const match = /<\|ACT\s*(?::\s*)?(\{[\s\S]*\})\|>/i.exec(content)
if (match && isValidJson(match[1])) { /* safe to parse */ }
function isValidJson(s: string): boolean {
try { JSON.parse(s); return true } catch { return false }
} Type guard
function isActPayloadShape(v: unknown): v is { emotion: { name: string; intensity?: number } } {
const e = (v as { emotion?: unknown })?.emotion
return (typeof e === 'string') || (!!e && typeof e === 'object' && typeof (e as { name?: unknown }).name === 'string')
} Try / catch
try {
const payload = JSON.parse(payloadText)
// shape checks...
} catch (e) {
// already the pattern in queues.ts: warn with payloadText, return ok:false
console.warn(`[parseActEmotion] bad payload: "${payloadText}"`, e)
} Prevention
- Constrain the model with strict ACT format examples in the system prompt.
- Parse defensively: tolerate truncated tags by regex-extracting the largest balanced {...} first.
- Never let ACT parsing throw upward — it is already inside the queue handler and must degrade to ok:false.
When it happens
Trigger: The model emits a tag like <|ACT {name: 'happy'} |> (single quotes / unquoted keys), truncates the JSON mid-stream (stop token, context limit), or interleaves commentary inside the braces; regex captures {...} greedily across newlines so stray braces pollute the group.
Common situations: Streaming output cut off before the closing tag; weaker models producing almost-JSON; prompt changes that make the model wrap the payload in prose.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- [llm-streaming-control] signal handler failed
- [llm-streaming-control] handler failed
- Expected `cap run --list --json` to return a JSON array.
- CAP_VITE_CAP_ARGS_JSON must be a JSON string array.
- Invalid chat session export format
AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18).
Data as JSON: /api/errors/c91a6c91746b655e.
Report an issue: GitHub.