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

  1. Log payloadText (already included in the warn) and identify the exact malformation.
  2. Tighten the system prompt / few-shot for the ACT tag format so JSON is strict (double quotes, no comments).
  3. Pre-sanitize the captured group (strip trailing commas, quotes normalization) or retry parse of the largest balanced {...} substring before giving up.
  4. 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

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

Related errors


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