Field guide · Pre-flight validation

How to validate a character card before uploading to Chub.ai or RisuAI.

A pre-flight ritual for your Tavern card — the ten failures that quietly break uploads to Chub.ai, RisuAI, and SillyTavern, the manual checklist that catches them, and the lint workflow you should automate once your library grows past a handful.

fol. iv.r

The last look before you ship

Open the support thread on any major card host and the same question repeats itself: my card uploaded but the greeting is empty, the lorebook didn’t come through, the image preview broke. Most of these threads end the same way too — a quiet edit, a re-export, a re-upload. The card itself was almost right.

Validation is the cheap habit that prevents all of it. Before you push a card to Chub.ai or import it into RisuAI, spend two minutes walking the field list. The same checklist scales: when you have ten cards you do it by eye, when you have a hundred you script it. This guide is both halves of that — what to look for, and what a linter should do on your behalf.

fol. v.r

Ten failures that break uploads

In rough order of how often they show up in community bug threads:

  1. Missing first_mes or empty mes_example. Hosts often accept the card but render a blank chat. RisuAI in particular leans on mes_example for tone priming; an empty string is not the same as a sensible default.
  2. Over-long description blowing the context budget. The card uploads cleanly, then the first generation hits the model with a prompt that’s mostly persona text and no room to actually reply. Token budgets vary by host, but broadly: keep the persistent block (description + personality + scenario + system_prompt) well under your target model’s context window.
  3. creator_notes confused with system_prompt. The first is human-facing — usage tips, content warnings, credits. The second is sent to the model on every turn. Swap them and you either leak meta-commentary into the conversation, or hide your jailbreak from the model entirely.
  4. Corrupted PNG tEXt chunk or wrong encoding. The historical convention is a chunk named chara, holding Base64-encoded UTF-8 JSON. Tools that write raw UTF-8 (no Base64), strip non-ASCII, or use the wrong chunk name will produce a PNG that opens in one client and silently fails in the next.
  5. Wrong type on character_book fields. Entries expect specific shapes — keys is an array of strings, not a comma-separated string;insertion_order is a number; enabled is a boolean. A lorebook that was hand-edited often picks up a stringified number or a flat string where an array is expected, and the importer drops the entry without warning.
  6. Custom keys under extensions that the target platform doesn’t recognise. A key written by SillyTavern (say a Regex pipeline config) is meaningless to Chub’s preview renderer. Nothing breaks, but the behaviour you tested locally is gone the moment the card moves.
  7. V3-only fields stuck inside a V2 envelope. nickname, group_only_greetings, creator_notes_multilingual are V3 additions. Drop them into a card whose spec still says chara_card_v2 and conforming clients are within their rights to ignore the lot.
  8. tags in the wrong shape. It is an array of strings. "tags": "fantasy, royalty" is a string and will either be rejected or get indexed as a single weird tag named “fantasy, royalty”.
  9. Empty strings inside alternate_greetings. An empty entry usually means a deleted draft you forgot to remove. Some clients render it as a blank message option; others throw. Either way it’s a noisy bug to ship.
  10. Host policy — rate limits, image size caps, NSFW filters. These aren’t schema problems and a local linter can’t fully catch them, but they cause as many failed uploads as any of the above. Know each host’s rules before you batch-upload at 3am.
fol. vi.r

Manual checklist

When you only have a handful of cards, an actual walk-through beats any tool. Tick these before you hit upload:

  • [ ] spec and spec_version agree (V2 with 2.0, V3 with 3.0).
  • [ ] name, description, first_mes all present and non-empty.
  • [ ] Persistent prompt block fits comfortably in your target context window, with room for chat history.
  • [ ] creator_notes contains no instructions intended for the model.
  • [ ] PNG opens, preview renders, and a quick hex glance shows a tEXt chunk with keyword chara.
  • [ ] character_book.entries[*].keys is an array of strings everywhere it appears.
  • [ ] tags is an array; alternate_greetings contains no empty strings.
  • [ ] No V3-only keys inside a V2 card (or vice versa).
  • [ ] extensions keys are documented; you know which host each one is for.
  • [ ] Target host’s upload rules (image size, NSFW policy, rate limit) checked for this batch.
fol. vii.r

What a linter should do for you

The checklist scales to about thirty cards before it stops being fun. After that, automation. A useful card linter does three jobs:

  • Schema validation. Parse the PNG, decode the tEXt chunk, validate the JSON against the declared spec. Required fields, types, enums, no stray V3 keys in V2 cards.
  • Host compatibility matrix. For every key in the card, report which target hosts actually consume it. A field that only SillyTavern reads isn’t broken — you just want to be told before you assume it travels.
  • Token budget estimate. Add up the persistent prompt block under a few common tokenisers and warn when it eats more of the context than your target model can spare.

This is exactly the niche tavernai.cards is being built into: a pre-flight workbench that runs the same checks you’d run by hand, against the same hosts you actually publish to.

Stop discovering broken cards in your upload history. Lint V2 and V3 cards against real-host quirks before you publish, and migrate between specs with a diff you can read.

Read in another tongue