{"record":{"id":"c91a6c91746b655e","repo":"moeru-ai/airi","slug":"parseactemotion-failed-to-parse-act-payload-json","errorCode":null,"errorMessage":"[parseActEmotion] Failed to parse ACT payload JSON: \"${payloadText}\"","messagePattern":"\\[parseActEmotion\\] Failed to parse ACT payload JSON: \"(.+?)\"","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui/src/composables/queues.ts","lineNumber":49,"sourceCode":"      const payload = JSON.parse(payloadText) as { emotion?: unknown }\n      const emotion = payload?.emotion\n      if (typeof emotion === 'string') {\n        const normalized = normalizeEmotionName(emotion)\n        if (normalized)\n          return { ok: true, emotion: { name: normalized, intensity: 1 } }\n      }\n      else if (emotion && typeof emotion === 'object' && !Array.isArray(emotion)) {\n        if ('name' in emotion && typeof (emotion as { name?: unknown }).name === 'string') {\n          const normalized = normalizeEmotionName((emotion as { name: string }).name)\n          if (normalized) {\n            const intensity = normalizeIntensity((emotion as { intensity?: unknown }).intensity)\n            return { ok: true, emotion: { name: normalized, intensity } }\n          }\n        }\n      }\n    }\n    catch (e) {\n      console.warn(`[parseActEmotion] Failed to parse ACT payload JSON: \"${payloadText}\"`, e)\n    }\n\n    return { ok: false, emotion: null as EmotionPayload | null }\n  }\n\n  return createQueue<string>({\n    handlers: [\n      async (ctx) => {\n        const actParsed = parseActEmotion(ctx.data)\n        if (actParsed.ok && actParsed.emotion) {\n          ctx.emit('emotion', actParsed.emotion)\n          emotionsQueue.enqueue(actParsed.emotion)\n        }\n      },\n    ],\n  })\n}\n","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-ui/src/composables/queues.ts#L31-L67","documentation":"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.","triggerScenarios":"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.","commonSituations":"Streaming output cut off before the closing tag; weaker models producing almost-JSON; prompt changes that make the model wrap the payload in prose.","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'."],"exampleFix":"// before\nconst payload = JSON.parse(payloadText) // throws on {name: 'happy'}\n\n// after\nconst payload = JSON.parse(payloadText.replace(/'/g, '\"').replace(/(\\w+)\\s*:/g, '\"$1\":'))","handlingStrategy":"validation","validationCode":"const match = /<\\|ACT\\s*(?::\\s*)?(\\{[\\s\\S]*\\})\\|>/i.exec(content)\nif (match && isValidJson(match[1])) { /* safe to parse */ }\nfunction isValidJson(s: string): boolean {\n  try { JSON.parse(s); return true } catch { return false }\n}","typeGuard":"function isActPayloadShape(v: unknown): v is { emotion: { name: string; intensity?: number } } {\n  const e = (v as { emotion?: unknown })?.emotion\n  return (typeof e === 'string') || (!!e && typeof e === 'object' && typeof (e as { name?: unknown }).name === 'string')\n}","tryCatchPattern":"try {\n  const payload = JSON.parse(payloadText)\n  // shape checks...\n} catch (e) {\n  // already the pattern in queues.ts: warn with payloadText, return ok:false\n  console.warn(`[parseActEmotion] bad payload: \"${payloadText}\"`, e)\n}","preventionTips":["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."],"tags":["llm","json","act-protocol","emotion","streaming"],"backgroundTag":"invalid-json-payload","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}