DEV Community

PicklePixel
PicklePixel

Posted on

How I Reverse-Engineered Claude Code's Hidden Pet System

The Buddy Creator web tool showing a shiny legendary cat with a tophat

I was poking around Claude Code's source one evening and found something I wasn't supposed to see: a full gacha companion pet system, hidden behind a compile-time feature flag. A little ASCII creature that sits beside your terminal input, occasionally comments in a speech bubble, and is permanently bound to your Anthropic account. Your buddy is deterministic. Same account, same pet, every single time. No rerolls.

Naturally, I wanted a legendary dragon. Here's how I cracked it.

What's Actually in There

The buddy system lives across four files inside Claude Code's codebase:

  • buddy/types.ts defines 18 species, 5 rarities, 6 eye styles, 8 hats, and 5 stats
  • buddy/companion.ts implements the PRNG, hash function, roll algorithm, and tamper protection
  • buddy/sprites.ts has ASCII art for every species (three animation frames each, a hat overlay system, and a render pipeline)
  • buddy/prompt.ts holds a system prompt that gets injected into Claude so it knows how to coexist with the pet without impersonating it

The feature is gated behind a BUDDY compile-time flag. When the flag is off, the entire thing gets dead-code-eliminated from the build. It was teased during the first week of April 2026 and is slated for a full launch in May. The /buddy slash command activates it when the flag is on.

Here's what the species look like as ASCII sprites:

DUCK                DRAGON              GHOST
    __              /^\  /^\            .----.
  <(· )___        <  ·  ·  >          / ·  · \
   (  ._>          (   ~~   )         |      |
    `--´            `-vvvv-´          ~`~``~`~
Enter fullscreen mode Exit fullscreen mode

Eighteen species total: duck, goose, blob, cat, dragon, octopus, owl, penguin, turtle, snail, ghost, axolotl, capybara, cactus, robot, rabbit, mushroom, and chonk. Each one has a compact face representation for inline display, three animation frames on a 500ms tick timer, and a hat overlay slot on line zero of the sprite.

One fun detail: every species name in the source code is obfuscated through String.fromCharCode() arrays. "Capybara" collides with an internal Anthropic model codename that's flagged in their repo's excluded-strings.txt, so they encoded all 18 species uniformly to keep their string-scanning tooling happy.

The Gacha Algorithm

Your buddy is a pure function of your identity. The algorithm chains together like this:

Account UUID (from OAuth)
    → concatenate with salt 'friend-2026-401'
    → hash to 32-bit integer
    → seed Mulberry32 PRNG
    → deterministic roll sequence
Enter fullscreen mode Exit fullscreen mode

The PRNG calls happen in strict order: rarity first, then species, then eye, then hat, then shiny, then stats. Changing any earlier roll changes everything after it.

Rarity weights:

Rarity Probability
Common 60%
Uncommon 25%
Rare 10%
Epic 4%
Legendary 1%

On top of that, there's a 1% shiny chance that rolls independently of rarity. A shiny legendary of a specific species? That's a 0.00056% probability, roughly 1 in 180,000.

Stats are shaped by rarity through a floor system. Legendaries start at a floor of 50 and always max out their peak stat at 100. Commons start at 5 and cap their peak around 84. Each companion gets one peak stat and one dump stat, with the rest falling somewhere in between.

There's an important hash function detail here. Claude Code runs in Bun, so the production hash is Bun.hash(), which is native C wyhash. The Node.js fallback is FNV-1a. These produce completely different values for the same input, which means any tooling running outside Bun cannot reproduce the exact buddy for a given account.

How the Tamper Protection Works

This is the part that got interesting. The buddy system splits companion data into two categories:

Stored in config (~/.claude.json): name, personality, hatchedAt timestamp. These are editable and meant to be personal.

Recomputed every read (called "bones"): rarity, species, eye, hat, shiny, stats. These are derived deterministically from your account hash on every single call to getCompanion().

The tamper protection comes down to a JavaScript spread operation:

export function getCompanion() {
  const stored = getGlobalConfig().companion
  if (!stored) return undefined
  const { bones } = roll(companionUserId())
  return { ...stored, ...bones }
}
Enter fullscreen mode Exit fullscreen mode

Because bones comes second in the spread, it always overwrites anything you manually added to the config. You can edit ~/.claude.json all you want, set rarity: "legendary", and it gets stomped on every read. The recomputed values win, period.

It's clever design. No server-side validation needed, no database, no "lost my save" support tickets. Your buddy is a pure function of your identity, recomputed every time it's needed. The bones are cached by userId + SALT key to avoid redundant computation on the three hot paths: the 500ms sprite tick, per-keystroke prompt input, and per-turn observer.

But here's the thing about client-side enforcement: it's client-side.

The Crack

The entire hack is swapping two variable names. In the minified v2.1.89 binary, getCompanion() compiles down to something like:

{bones:$}=Gh$(Th$());return{...H,...$}
Enter fullscreen mode Exit fullscreen mode

H is the stored config, $ is the recomputed bones. Bones come last, bones win. To flip that:

{bones:$}=Gh$(Th$());return{...$,...H}
Enter fullscreen mode Exit fullscreen mode

Now stored config comes last. Config wins. Whatever you write to ~/.claude.json takes priority over the recomputed values.

The two strings are the exact same byte length, so there's zero offset shift in the binary. No padding, no realignment, no relocation table headaches. You find the pattern, swap H and $, write it back. That's the whole patch.

I wrote a Node.js patcher that automates the whole thing in a single command. Design your buddy on the web creator, copy the JSON, run node buddy-crack.js, and it patches the binary and injects your companion in one step. It auto-reads from your clipboard, so you don't even need to pass arguments. That was a deliberate choice: Windows CMD chokes on JSON in command-line arguments because of quote conflicts, so clipboard-first was the only sane default.

The patcher went through a few iterations that taught me things the hard way.

The first version had separate patch and inject commands, which was unnecessarily complex for something that always happens together. Collapsed that into a single flow early on.

Then I nearly destroyed my own Claude Code config. The original config writer would parse ~/.claude.json, fail on any syntax weirdness, fall back to an empty object, and write that back with just the companion data. That nuked everything else in the file: OAuth tokens, permissions, theme settings, tool approvals. On a config that can easily be 50KB, that's catastrophic. The fix was to make the injector surgical. It tries a proper JSON parse first, but if that fails, it now uses a brace-depth parser to find and replace just the companion field in the raw string, leaving everything else untouched. It only creates a fresh file as a last resort, and it backs up ~/.claude.json to .claude.json.bak before touching anything.

Windows threw another curveball. PowerShell's Get-Clipboard mangles UTF-8 characters, so the star eye character would come through as ?. The fix forces UTF-8 output encoding from PowerShell and auto-repairs known corrupted characters on paste.

The final round of hardening added binary integrity checks (verifying file size after write to catch truncated writes) and auto-restore from backup if the patch fails mid-write. The patcher now handles XDG-standard paths across all three platforms and scans the Claude Code versions directory for additional binaries to patch.

Building the Web Creator

Reading the source also meant I had all the sprite data, so I built a web-based companion designer. Single HTML file, no dependencies, no build system. You pick your species from a grid that shows the actual ASCII faces, choose your rarity, eyes, hat, toggle shiny, and it renders a live preview of the full 5-line sprite with your selections applied.

There's a soul section with two options: generate a name and personality by pasting a prompt into Claude or ChatGPT, or just type them yourself. Hit "Copy Config JSON" and it exports exactly what the patcher expects. The install guide is built into the page with platform-specific instructions for Windows, macOS, and Linux.

The whole thing lives at pickle-pixel.com/buddy. The usage flow is four steps: design your companion, close Claude Code, run the patcher, restart.

The Attack Surface

While I was documenting everything, I mapped out every possible angle someone might try:

Attack Works? Why
Edit config fields Name/personality only Bones always overwritten
Change accountUuid No Server validates on auth
Patch the binary Yes That's what this tool does
Create new accounts Uncontrolled Can't choose your UUID
Brute-force UUIDs Statistically But you can't use found UUIDs

The brute-force angle is interesting. I wrote a separate script that replicates the full gacha algorithm and generates random UUIDs to find legendary rolls. It works statistically, but the UUIDs it finds are useless in practice because Anthropic assigns them server-side during account creation. You don't get to pick yours.

And there's the Bun versus Node.js hash problem again. The brute-forcer runs in Node.js by default, using FNV-1a, but production Claude Code uses Bun's wyhash. The probability distributions are identical, but per-UUID results won't match unless you run the script under Bun.

What I Learned

The buddy system is genuinely well-designed for what it's trying to do. Deterministic gacha with no server state is elegant. The tamper protection through spread ordering is simple and effective against casual editing. The soul/bones split lets users personalize their pet's name and personality while keeping the visual identity locked to their account.

But any system where the enforcement happens entirely on the client has a fundamental limit. The binary is on your machine. The config is on your machine. The merge logic is one spread operation in a JavaScript function. The crack is five characters swapped in a compiled binary.

That said, I don't think the Anthropic team is under any illusion that this is uncrackable. Deterministic client-side gacha is a design choice that trades tamper-resistance for zero-server-cost operation. No database, no API calls to validate rarity, no sync issues. For a fun companion pet feature in a CLI tool, that's the right tradeoff. The buddy system doesn't gate any functionality. It's a toy, and it's a charming one.

The code is at github.com/Pickle-Pixel/claudecode-buddy-crack if you want to pick your own companion. The full reverse-engineering documentation is in BUDDY_SYSTEM.md.

Now if you'll excuse me, I have a legendary shiny dragon to go look at.

Top comments (0)