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