Back to blog

How We Made Claude Code /buddy Not Random

Claude Code's new /buddy feature looks random, but the companion roll is deterministic. Here is how we made it configurable and shipped ccbuddy.dev in a weekend.

Martin Vančo
How We Made Claude Code /buddy Not Random

Anthropic launched /buddy on April 1 as a tiny terminal Tamagotchi inside Claude Code. You run one command, and a companion appears with a species, eyes, hat, rarity, and a handful of stats.

It looked random. It looked collectible. It looked like one of those features that was probably going to disappear after the joke landed.

So we did the obvious thing: we reverse-engineered it and built CCBuddy, a tool that lets you pick the buddy you actually want.

The fun part: the roll is not really random

The first interesting detail is that Claude Code does not generate a truly random companion every time you run /buddy.

The result is deterministic. The buddy is derived from a seed based on your user identity plus a hardcoded salt embedded in the Claude Code binary. Once that seed exists, a pseudorandom generator walks through the companion attributes in order:

  1. rarity
  2. species
  3. eyes
  4. hat
  5. shiny flag
  6. stats

At a high level, the logic looks like this:

seed = hash(userId + salt)
rng = mulberry32(seed)
rarity = rollWeighted(rng)
species = pick(speciesPool, rng)
eyes = pick(eyePool, rng)
hat = pick(hatPool, rng)
shiny = rollOnePercent(rng)
stats = rollStats(rng)

That changes the problem completely.

If the outcome depends on a deterministic userId + salt input, then you do not need to fight randomness. You need to control the salt.

The CLI approach: patch the salt, keep the rest

That became the basis for the CLI.

ccbuddyy finds the installed Claude Code binary, backs it up, replaces the hardcoded salt with a new one, re-signs the binary on macOS, and clears the locally cached buddy so Claude Code hatches a new one on the next /buddy run.

The core idea is simple:

  • keep Claude Code's real companion logic intact
  • change only the seed input
  • search for a salt that produces the exact buddy spec you want

That means users can request things like:

npx ccbuddyy build -species dragon -rarity legendary
npx ccbuddyy build -species penguin -rarity legendary -shiny
npx ccbuddyy build -species cat -rarity epic -eye star -hat crown

Under the hood, the builder brute-forces candidate salts until the rolled output matches the requested combination.

This works because /buddy rarity and cosmetics are not server-assigned rewards. They are local deterministic output.

Why this was worth building at all

The joke only works because the feature is genuinely good.

/buddy is silly in the right way. It makes a terminal tool feel alive. It creates attachment. It gives people something to show, compare, and talk about. That is why the first reaction from a lot of Claude Code users was not just "haha". It was "wait, I want a better one."

The problem is that pure deterministic assignment kills the collectible fantasy once you understand it. If your account hash decides that you are forever getting a common turtle with no hat, there is no progression, no trading, and no discovery loop. There is just destiny.

CCBuddy exists because the product idea is stronger than the one-off implementation.

We also built a landing page fast enough to match the meme

The CLI solved the technical problem, but people still needed a smoother way to explore the space. That became ccbuddy.dev.

The site was built as a terminal-styled Nuxt app with a deliberately opinionated interface:

  • a dark ANSI-inspired palette
  • animated ASCII sprites for all 18 species
  • a weighted random roll widget that mirrors Claude Code odds
  • predefined companion cards with copy-to-terminal commands
  • a multi-step builder for species, rarity, eyes, and hats

The design goal was not "marketing site" in the generic SaaS sense. It had to feel like an extension of the joke: terminal-native, a little obsessive, and instantly understandable.

That drove a lot of specific implementation decisions.

The builder had to feel playful, not form-like

The site ships with 18 animated species, 6 eye types, 8 hat variants, rarity-colored cards, and a builder that progressively narrows your choice.

A few details mattered more than they look:

  • Weighted rarity display. The random roll widget uses the same 60/25/10/4/1 rarity odds as the original companion system.
  • Click-to-copy everywhere. Every interesting state ends in a terminal command, not a dead-end preview.
  • One-screen wizard. The full-screen builder shows live previews as you move through species, rarity, eyes, and hats.
  • Terminal visual language. Borders, glow colors, and sprite rendering all stay anchored to the Claude Code aesthetic.

That last point matters. If the interaction feels too polished in a generic consumer-web way, the idea gets weaker. This product works because it still feels like a terminal toy, just one you can control.

The annoying bugs were exactly the kind of bugs weekend launches create

The build log for the landing page is a perfect summary of what "fast" actually means in practice.

A few examples:

Tailwind production colors drifted

The legendary color rendered correctly in development and then silently turned into Tailwind's default yellow in production. The root causes were boring but real: an overridden content config, a TypeScript Tailwind config in Docker, and stale CSS from the deploy cache.

v-html inside <pre> beat v-for

The rainbow /buddy hero headline originally used spans inside a <pre> block, which introduced visible whitespace between characters. The fix was to render the ANSI-like rainbow text as a single computed HTML string.

Sprite animation needed strict frame height

A few species had frame-to-frame height jumps because decorative effects changed the number of visible rows. The practical fix was to normalize every frame to a stable line count so the cards did not visibly jump.

These are not glamorous problems, but they are the real texture of shipping a tiny product quickly. The core reverse-engineering idea is what gets attention. The thousand visual and deployment details are what make the thing usable.

The bigger lesson is that Claude Code is unusually hackable

One reason this project came together quickly is that Claude Code has a lot of local, inspectable structure.

That shows up in multiple places:

  • transcript files are local and readable
  • command behavior can often be traced from actual implementation details
  • seemingly random UI outcomes are sometimes deterministic under the hood

That is also why so much of the Vibenalytics blog ends up orbiting Claude Code internals. Once you start inspecting how the tool actually works, you keep finding systems that are simpler, weirder, and more legible than expected.

/buddy is a perfect example. On the surface it looks like a novelty feature. Underneath, it is a deterministic generator with a patchable input and a surprisingly rich design space.

Want more Claude Code internals?

Vibenalytics turns Claude Code usage into long-term history and project-level analytics, and this blog keeps documenting the mechanics underneath the product.

Open Vibenalytics