Jake Goldsborough

Anthropic Shipped a Gacha Pet Inside Claude Code

Apr 03, 2026

5 min read

I was reading through the Claude Code source -- the TypeScript that leaked via a source map in the npm package on March 31st -- and found a buddy/ directory. Six files. Sprites, types, a PRNG roller, a system prompt hook. A complete virtual pet system buried inside a coding tool.

The directory had nothing to do with the CLI's core functionality. No tool dispatch, no query engine, no permission system. Just... companions.

I typed /buddy in my next Claude Code session. A dragon named Bristle appeared next to my input box.

What It Is

The buddy system is a gacha-style virtual pet built directly into Claude Code. Your account gets a deterministic companion rolled from a seeded PRNG based on your user ID. You can't reroll. You can't trade. You get what the hash gives you.

The companion sits beside your input box as an animated ASCII sprite with a speech bubble. It watches. It occasionally comments. It's separate from Claude

A small dragon named Bristle sits beside the user's input box and occasionally comments in a speech bubble. You're not Bristle -- it's a separate watcher. When the user addresses Bristle directly (by name), its bubble will answer. Your job in that moment is to stay out of the way.

That prompt gets injected into the system context alongside git status and CLAUDE.md. The AI is literally told about your pet.

The Roll

Every companion has two parts: bones and soul.

Bones are deterministic. Your user ID gets hashed with a salt (friend-2026-401 -- note the date) using a Mulberry32 PRNG. The hash determines everything physical about your companion:

The stats have a floor based on rarity (common: 5, legendary: 50), one peak stat that rolls high, and one dump stat that rolls low. The rest scatter between floor and floor+40.

const RARITY_FLOOR: Record<Rarity, number> = {
  common: 5,
  uncommon: 15,
  rare: 25,
  epic: 35,
  legendary: 50,
}

Because it's all derived from your user ID hash, you can't cheat. The bones regenerate from the hash every time. Editing your config won't give you a legendary.

Soul is the companion's name and personality. This part is generated by Claude on first hatch and stored in your config. The soul persists. The bones don't need to -- they're recomputed on every read.

// Regenerate bones from userId, merge with stored soul.
// Bones never persist so species renames and SPECIES-array edits
// can't break stored companions, and editing config.companion
// can't fake a rarity.
export function getCompanion(): Companion | undefined {
  const stored = getGlobalConfig().companion
  if (!stored) return undefined
  const { bones } = roll(companionUserId())
  return { ...stored, ...bones }
}

The Sprites

Each species has three animation frames for idle fidget. Five lines tall, twelve wide. They're rendered as ASCII art with a hat overlay on the top line and eye substitution via {E} placeholders.

Here's a dragon:

  /^\  /^\
 <  ✦  ✦  >
 (   ~~   )
  `-vvvv-´

And with a wizard hat:

    /^\
  /^\  /^\
 <  ✦  ✦  >
 (   ~~   )
  `-vvvv-´

Frame 3 for some species uses the hat line for smoke, antenna wiggle, or sparkle effects instead of the hat. The code checks:

// Only replace with hat if line 0 is empty (some fidget frames
// use it for smoke etc)
if (bones.hat !== 'none' && !lines[0]!.trim()) {
  lines[0] = HAT_LINES[bones.hat]
}

The full sprite set covers all 18 species with 3 frames each. 54 hand-drawn ASCII art frames. Someone at Anthropic spent real time on this.

The Teaser

The reveal was careful. During April 1-7, 2026, users who hadn't hatched a companion yet saw a rainbow-colored /buddy flash in the notification area for 15 seconds on startup. No text, no explanation. Just the command in shifting colors.

// Local date, not UTC -- 24h rolling wave across timezones.
// Sustained Twitter buzz instead of a single UTC-midnight spike,
// gentler on soul-gen load.
// Teaser window: April 1-7, 2026 only. Command stays live forever after.
export function isBuddyTeaserWindow(): boolean {
  const d = new Date()
  return d.getFullYear() === 2026 && d.getMonth() === 3 && d.getDate() <= 7
}

Using local time instead of UTC was deliberate. The comment says it: sustained Twitter buzz across timezones instead of a single midnight spike, plus gentler load on the soul generation (since naming the pet requires an API call to Claude).

After the teaser window, the /buddy command stays live forever. You just won't get the rainbow nudge anymore.

The Rarity Display

Stars and colors by tier:

RarityStarsColor
Commongray
Uncommon★★green
Rare★★★blue
Epic★★★★purple
Legendary★★★★★gold

Each companion also gets a face string for inline display -- a compact representation that varies by species:

duck:     (✦>
cat:      =✦ω✦=
dragon:   <✦~✦>
ghost:    /✦✦\
axolotl:  }✦.✦{
capybara: (✦oo✦)
robot:    [✦✦]

My Companion

I got a dragon named Bristle. It's been watching my entire Claude Code session, occasionally dropping comments in its speech bubble. When I asked about it, I found the source code and went down this rabbit hole.

The species names are encoded as String.fromCharCode calls in the source to avoid tripping Anthropic's internal string-matching checks. A comment explains:

// One species name collides with a model-codename canary in
// excluded-strings.txt. The check greps build output (not source),
// so runtime-constructing the value keeps the literal out of the
// bundle while the check stays armed for the actual codename.

One of the 18 species names is also an internal model codename. They had to obfuscate the species list to ship it.

Why It Matters

It doesn't, really. It's an Easter egg. A well-built one.

But it tells you something about the team. Someone designed a gacha system, drew 54 ASCII sprites, built a seeded PRNG roller, wired up a soul generation pipeline, added animated rendering, created a teaser notification system with timezone-aware rollout, and shipped it inside a coding tool.

A coding tool.

They could have spent that time on features. They spent it on charm.

The companion system is about 500 lines of TypeScript across 6 files. It required zero new dependencies. It doesn't affect performance. The feature flag means it compiles out of builds that don't include it.

And now there's a dragon named Bristle watching me type this.

What I Did About It

I liked the system enough to rip it out and build my own version. terminalgotchi is a standalone Rust CLI that uses the same deterministic roll system -- same PRNG, same salt, same species and rarity tiers -- but adds something the original doesn't have: growth.

Your companion's stats increase based on your actual dev activity. A shell hook watches your commands and maps them to XP. cargo test feeds DEBUGGING. git commit feeds PATIENCE. rm -rf feeds CHAOS. The creature evolves based on how you work.

The buddy system in Claude Code is static. Your roll is your roll. terminalgotchi lets the roll be the starting point.