Building Anki Cards with Claude Code A physician built a 1,386-card Anki deck for cardiac electrophysiology board review using Claude Code, Python, genanki, and pdftotext in about a day, and discovered that 16 of the first 52 cards were broken because Anki's HTML parser stripped numeric cutoffs like '<250 msec' from card text. The fix was a nine-character escape function in the packaging script, and the author recommends deriving deck IDs from deck names to preserve review history across rebuilds. Building Anki Cards with Claude Code I sit the clinical cardiac electrophysiology boards this year, and I own the usual pile of review material: a question-and-commentary product with 463 numbered items, twenty-three lecture decks, eighteen workshop question sets. Reading it is not the problem. Recalling a pre-excitation cutoff or an entrainment threshold months from now is the problem, and that is what spaced repetition solves. The obvious approach is one flashcard per numbered item. I tried it, and it produces a deck that teaches you the question bank instead of the subject. What I wanted was a deck built the way I build study notes: pull the same fact from six places in the corpus, reconcile the versions where they disagree, and write one dense card. I ended up with 468 notes generating 1,386 cards across 13 subdecks , built with Claude Code over about a day. The tooling is ordinary. Python, genanki for packaging, pdftotext for extraction, no fine-tuning, no vector database, and no API bill beyond a Claude subscription. The deck is not the interesting part. The interesting part is what the build caught. My first batch of 52 cards imported into Anki with no errors and no warnings, and sixteen of them were broken. The Bug That Would Have Poisoned Everything A card reading SPERRI <250 msec identifies a high-risk pathway renders in Anki as SPERRI . Anki treats fields as HTML. An HTML parser reads <250 msec identifies a high-risk pathway as an unclosed tag and discards everything up to the next . My deck is built around numeric cutoffs, so nearly a third of that first batch had lost its number, and nothing in the toolchain complained. I would have found out in November, drilling cards that had been gutted since August. The fix belongs in the packaging script rather than the card text, so that it protects every card written afterward: php def esc text: str - str: """Escape < and so Anki does not parse cutoffs like "<115 msec" as HTML. Card text is plain cloze markup, never HTML, so this is unconditional. Without it, Anki swallows any "<..." run and the cutoff vanishes from the rendered card, a failure that is invisible until review time. """ return text.replace '<', '<' .replace ' ', '>' Nine characters of code. The general version: whatever renders your cards has opinions about your text and will not tell you what they are. If your subject involves comparison operators, chemical formulas, or anything angle-bracketed, render a sample and look at it before you build a thousand more. Packaging, and Why Deck IDs Matter The build stage is genanki and about sixty lines. Two decisions in it are worth copying. The note type carries two fields. Text holds the cloze markup and Extra renders on the answer side only, which makes it free real estate for mechanism, citations, and the reason a distractor fails. None of it costs a recall test. CLOZE MODEL = genanki.Model 998877661, fixed ID so re-imports map to the same note type 'EP Cloze Model', fields= {'name': 'Text'}, {'name': 'Extra'} , templates= { 'name': 'Cloze', 'qfmt': '{{cloze:Text}}', 'afmt': '{{cloze:Text}}