{"record":{"id":"727ef4608f6b829f","repo":"moeru-ai/airi","slug":"invalid-message-example-format-line","errorCode":null,"errorMessage":"Invalid message example format: ${line}","messagePattern":"Invalid message example format: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/stage-ui/src/stores/modules/airi-card.ts","lineNumber":424,"sourceCode":"        notesMultilingual: ccv3Card.data.creator_notes_multilingual,\n        personality: ccv3Card.data.personality ?? '',\n        scenario: ccv3Card.data.scenario ?? '',\n        greetings: [\n          ccv3Card.data.first_mes,\n          ...(ccv3Card.data.alternate_greetings ?? []),\n        ],\n        greetingsGroupOnly: ccv3Card.data.group_only_greetings ?? [],\n        systemPrompt: ccv3Card.data.system_prompt ?? '',\n        postHistoryInstructions: ccv3Card.data.post_history_instructions ?? '',\n        messageExample: ccv3Card.data.mes_example\n          ? ccv3Card.data.mes_example\n              .split('<START>\\n')\n              .filter(Boolean)\n              .map(example => example.split('\\n')\n                .map((line) => {\n                  if (line.startsWith('{{char}}:') || line.startsWith('{{user}}:'))\n                    return line as `{{char}}: ${string}` | `{{user}}: ${string}`\n                  throw new Error(`Invalid message example format: ${line}`)\n                }))\n          : [],\n        tags: ccv3Card.data.tags ?? [],\n        extensions: {\n          ...ccv3Card.data.extensions,\n          airi: resolveAiriExtension(ccv3Card),\n        },\n      }\n    }\n\n    return {\n      ...card,\n      extensions: {\n        ...card.extensions,\n        airi: resolveAiriExtension(card),\n      },\n    }\n  }","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/moeru-ai/airi/blob/f679616c34f1cf6d282c8d64264242af944b3fed/packages/stage-ui/src/stores/modules/airi-card.ts#L406-L442","documentation":"newAiriCard parses SillyTavern V3 character cards (ccv3): mes_example is split on the literal '<START>\\n' and every remaining line must start with '{{char}}:' or '{{user}}:'. Any other line — a blank line, narration text, or a block produced by a '<START>' separator with different whitespace or line endings — throws with the offending line in the message. This makes loosely-authored real-world cards fail import outright.","triggerScenarios":"Importing a card whose example dialogs contain blank lines between turns or narration lines; mes_example using CRLF so the split on '<START>\\n' leaves stray '\\r' or unmatched separators; examples separated by '<START>' without the required trailing newline; hand-edited or third-party tool exports with free-form text inside examples.","commonSituations":"Downloading community character cards that render fine in SillyTavern but contain formatting the strict parser rejects; authors pasting dialogue with empty lines; cards authored on Windows editors inserting CRLF.","solutions":["Pre-normalize mes_example before import: split on /<START>\\r?\\n/, trim lines, and drop or fix lines that lack the {{char}}:/{{user}}: prefix.","Edit the card JSON directly and make every line inside mes_example start with '{{char}}:' or '{{user}}:'.","Wrap the import in try/catch and show the offending line (already included in the error message) to the user.","File an issue or relax the parser upstream (skip/blank-tolerant parsing) if strictness is not a product requirement."],"exampleFix":"// before\nconst card = newAiriCard(ccv3)\n\n// after - normalize before parsing so strict line rules pass\nfunction normalizeMesExample(raw: string | undefined): string | undefined {\n  if (!raw) return undefined\n  const cleaned = raw\n    .split(/<START>\\r?\\n/)\n    .filter(Boolean)\n    .map(block => block\n      .split(/\\r?\\n/)\n      .filter(line => line.startsWith('{{char}}:') || line.startsWith('{{user}}:'))\n      .join('\\n'))\n    .filter(Boolean)\n    .join('<START>\\n')\n  return cleaned || undefined\n}\n\nconst card = newAiriCard({\n  ...ccv3,\n  data: { ...ccv3.data, mes_example: normalizeMesExample(ccv3.data.mes_example) },\n})","handlingStrategy":"validation","validationCode":"function hasValidMesExample(mesExample: string | undefined): boolean {\n  if (!mesExample) return true // absent is fine\n  return mesExample\n    .split('<START>\\n')\n    .filter(Boolean)\n    .every(block => block\n      .split('\\n')\n      .every(line => line.startsWith('{{char}}:') || line.startsWith('{{user}}:')))\n}\n\nif (!hasValidMesExample(ccv3.data.mes_example))\n  ccv3 = normalizeCardBeforeImport(ccv3) // strip/skip offending lines\nconst card = newAiriCard(ccv3)","typeGuard":"function isParsableMesExampleLine(line: string): line is `{{char}}: ${string}` | `{{user}}: ${string}` {\n  return line.startsWith('{{char}}:') || line.startsWith('{{user}}:')\n}","tryCatchPattern":"try {\n  const card = newAiriCard(ccv3)\n}\ncatch (error) {\n  const message = errorMessageFrom(error) ?? ''\n  if (message.startsWith('Invalid message example format:')) {\n    // the offending line is embedded in the message; show it for repair\n    showImportError(`Fix the example dialog line: ${message.split(': ').slice(1).join(': ')}`)\n    return\n  }\n  throw error\n}","preventionTips":["Pre-validate mes_example line prefixes before import; normalize CRLF and blank lines.","When authoring cards, keep every example line prefixed with {{char}}: or {{user}}:.","Use the exact '<START>\\n' separator between example blocks.","Report the offending line (already in the error) instead of a generic import failure."],"tags":["character-card","parsing","import","validation","sillytavern"],"backgroundTag":"character-card-parse-error","analyzedSha":"f679616c34f1cf6d282c8d64264242af944b3fed","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}