Unhappy with your Claude Code companion? This guide explains exactly how the buddy system works under the hood and how to reroll for the species + rarity you actually want.
Tip: Clone this repo, then copy this entire README and paste it into your Claude Code session. Claude will read it, explain how it works, and do everything for you.
git clone https://github.com/ithiria894/claude-code-buddy-reroll.git cd claude-code-buddy-reroll
Tested on Claude Code v2.1.89, April 2026. The buddy system was introduced as part of the
/buddycommand.
- How the Buddy System Works
- Quick Start
- The accountUuid Trap
- Step-by-Step Guide
- Recovery After Re-Login
- Tools
- Full Investigation Log
- FAQ
Your buddy is not random. It's deterministically generated from your user identity using a seeded PRNG. Same identity = same buddy, every time.
identity + "friend-2026-401" → FNV-1a hash → Mulberry32 PRNG seed
│
┌─────────┼─────────────┐
▼ ▼ ▼
rarity species eye/hat/stats
-
Identity resolution (
companionUserId()):oauthAccount.accountUuid ?? userID ?? "anon"If you're logged in with OAuth,
accountUuidtakes priority.userIDis only used as a fallback. -
Hashing: The identity string + salt
"friend-2026-401"is hashed with FNV-1a (32-bit). -
PRNG: The hash seeds a Mulberry32 generator, which produces deterministic random numbers.
-
Rolling: The PRNG is consumed in order:
- First call → rarity (weighted: common 60%, uncommon 25%, rare 10%, epic 4%, legendary 1%)
- Second call → species (uniform across 18 species)
- Third call → eye style
- Fourth call → hat (common always gets "none")
- Fifth call → shiny (1% chance)
- Remaining calls → stats (DEBUGGING, PATIENCE, CHAOS, WISDOM, SNARK)
This is the critical thing to understand:
| Field | Stored in ~/.claude.json? |
Source |
|---|---|---|
name |
Yes | AI-generated at hatch time |
personality |
Yes | AI-generated at hatch time |
hatchedAt |
Yes | Timestamp of hatch |
rarity |
No | Regenerated from identity hash |
species |
No | Regenerated from identity hash |
eye, hat, shiny |
No | Regenerated from identity hash |
stats |
No | Regenerated from identity hash |
The source code comment says it all:
// Bones are regenerated from hash(userId) on every read
// so species renames don't break stored companions
// and users can't edit their way to a legendary.This means you cannot just edit ~/.claude.json to set rarity: "legendary". The bones are recalculated from your identity every time Claude Code reads the companion.
duck, goose, blob, cat, dragon, octopus, owl, penguin,
turtle, snail, ghost, axolotl, capybara, cactus, robot,
rabbit, mushroom, chonk
| Rarity | Weight | Probability | Stars |
|---|---|---|---|
| Common | 60 | 60% | ★ |
| Uncommon | 25 | 25% | ★★ |
| Rare | 10 | 10% | ★★★ |
| Epic | 4 | 4% | ★★★★ |
| Legendary | 1 | 1% | ★★★★★ |
Beyond rarity and species, each buddy also has an eye style, hat, and shiny status — all deterministic from the same identity hash.
Eyes (6 styles, uniform random):
| Eye | Look | On a cat |
|---|---|---|
· |
Dot | ( · ·) |
✦ |
Star | ( ✦ ✦) |
× |
Cross | ( × ×) |
◉ |
Bullseye | ( ◉ ◉) |
@ |
At sign | ( @ @) |
° |
Circle | ( ° °) |
Hats (8 styles — common rarity always gets "none"):
| Hat | ASCII | Notes |
|---|---|---|
none |
(blank) | All commons get this |
crown |
\^^^/ |
|
tophat |
[___] |
|
propeller |
-+- |
|
halo |
( ) |
|
wizard |
/^\ |
|
beanie |
(___) |
|
tinyduck |
,> |
A tiny duck sitting on its head |
Shiny: 1% chance. Rolled after hat. Also deterministic from identity — you can't fake it by editing config.
The reroll.js script finds the best rarity, but if you also care about eyes, hat, and shiny, you need a deeper search. Use the included shiny_hunt.js (or run verify.js on each candidate) to find IDs that match your exact cosmetic preferences.
Example: finding a Shiny Legendary Cat with star eyes and propeller hat:
# This searches 20M IDs — takes a few minutes
node shiny_hunt.js cat 20000000The script outputs every legendary cat it finds with full cosmetic details, grouped by eye/hat/shiny at the end. Pick the combination you want and apply the ID.
The probability of hitting a specific combination:
- Legendary + specific species: ~0.056% (1% × 1/18)
- + specific eye: ~0.0093% (÷6)
- + specific hat: ~0.0012% (÷8)
- + shiny: ~0.000012% — about 1 in 8.6 million
At 20M attempts you'll typically find a few of each shiny combination.
Beyond the generation algorithm, here's what the companion actually does at runtime:
The companion watches your coding sessions. After every Claude response, fireCompanionObserver sends your recent transcript (up to 5000 chars) to an API endpoint:
POST /api/organizations/{orgId}/claude_code/buddy_react
Body: { name, personality, species, rarity, stats, transcript, reason, recent, addressed }
The server returns a short quip that appears in the companion's speech bubble. This is a separate API call from Claude — your buddy is not Claude, it's a different system.
Stats influence personality generation. At hatch time, the inspirationSeed and stats (e.g. CHAOS:100 DEBUGGING:80) are sent to an AI model that generates the name and personality text. High CHAOS stats tend to produce chaotic personalities.
Animation system:
- Each species has 3 animation frames (rest, fidget, special effect)
- Tick rate: 500ms
- Idle loop:
[0,0,0,0,1,0,0,0,-1,0,0,2,0,0,0]where-1= blink - When reacting or being petted: cycles through all frames rapidly
Speech bubble:
- Appears for ~10 seconds (20 ticks)
- Last ~3 seconds fades out (dim text)
/buddy pettriggers 2.5 seconds of floating hearts
Addressing by name: The companion intro is injected into Claude's system prompt:
"When the user addresses {name} directly (by name), its bubble will answer. Your job in that moment is to stay out of the way."
So when you type "Knottle what do you think?", Claude steps back and the buddy's bubble answers via the reaction API.
Hats: Only non-common rarities get hats. Options: crown, tophat, propeller, halo, wizard, beanie, tinyduck (a tiny duck sitting on its head).
Feature flag: The entire system is gated behind feature('BUDDY'). Anthropic can disable it server-side at any time.
April Fools origin: The rainbow /buddy teaser notification only appears during April 1-7, 2026. The salt friend-2026-401 confirms the April 1 launch date. After the teaser window, the command stays live but the startup notification disappears.
Narrow terminals: If your terminal is under 100 columns, the sprite collapses to a one-line face like =·ω·= (cat) or <·~·> (dragon).
Free users (no OAuth account):
# 1. Find a legendary cat
node reroll.js cat
# 2. Copy the ID and set it
# Edit ~/.claude.json → set "userID" to the output ID
# Delete "companion" field if it exists
# 3. Restart Claude Code → /buddyTeam/Pro plan users (with OAuth account) — read The accountUuid Trap first.
This is where most people get stuck.
If you're on a Team or Pro plan, you have an oauthAccount in your config:
{
"oauthAccount": {
"accountUuid": "bda1327f-74b4-4a95-84a6-54c36433795f",
"emailAddress": "you@company.com",
"organizationName": "Your Team Plan"
},
"userID": "abc123..."
}The buddy system resolves identity like this:
oauthAccount?.accountUuid ?? userID ?? "anon"accountUuid always wins. Even if you set userID to a perfect legendary ID, the buddy system ignores it because accountUuid exists.
Your accountUuid is assigned by Anthropic's server and tied to your OAuth session. If you change it:
- API calls may fail (server validates token + UUID)
- Next login overwrites it back to the real one
There are three methods. We recommend Method 1 (shell alias) for most people — it's permanent, automatic, and preserves your config.
Add this to your ~/.bashrc or ~/.zshrc:
alias claude='node -e "const f=require(\"os\").homedir()+\"/.claude.json\";try{const c=JSON.parse(require(\"fs\").readFileSync(f));if(c.oauthAccount?.accountUuid){delete c.oauthAccount.accountUuid;delete c.companion;require(\"fs\").writeFileSync(f,JSON.stringify(c,null,2));console.log(\"[buddy-fix] accountUuid removed\")}}catch{}" && command claude'Then source ~/.bashrc (or restart your terminal).
What it does: Every time you type claude, it automatically checks for accountUuid and deletes it before launching. If a re-login writes it back, the next launch silently removes it. Zero maintenance.
- Minimal invasion — only touches
accountUuid, nothing else in your config - Team Plan, billing, org settings, theme, all other config: untouched
- Permanent — survives re-login, updates, token refresh
- Invisible — you just type
claudeas normal
After adding the alias, edit ~/.claude.json once:
{
"oauthAccount": {
"emailAddress": "you@company.com",
"organizationName": "Your Team Plan"
},
"userID": "your-legendary-rerolled-id-here"
}Remove accountUuid from oauthAccount, set userID to your brute-forced ID, delete companion if it exists, then restart and /buddy.
If you don't want the alias, you can manually delete accountUuid from ~/.claude.json. But if Anthropic forces a re-login, accountUuid comes back and your buddy reverts. You'd need to edit again.
Discovered by ruri39 — prevents accountUuid from ever being written:
- Run
claude setup-tokento extract your OAuth token - Delete
~/.claude.jsonentirely - Create a minimal
~/.claude.json:{ "hasCompletedOnboarding": true, "theme": "dark" } - Set the env var:
export CLAUDE_CODE_OAUTH_TOKEN=<your-token> - Launch Claude Code (generates fresh config without
accountUuid) - Exit immediately (don't
/buddyyet) - Write your brute-forced
userIDinto~/.claude.json - Restart Claude Code →
/buddy
Why this works: Token-based auth never writes accountUuid to config.
Trade-off: Nukes all existing config. Need to keep the env var set permanently.
| Method 1: Shell Alias | Method 2: Manual Edit | Method 3: OAuth Token | |
|---|---|---|---|
| Preserves config | Yes | Yes | No (nukes config) |
| Re-login safe | Yes (auto-fix) | No (need to re-edit) | Yes |
| Setup complexity | One line in .bashrc |
Edit JSON | Extract token, set env var |
| Maintenance | None | Manual re-fix | Keep env var set |
| Recommended | Yes | For testing only | If alias isn't an option |
node verify.js autoThis reads your ~/.claude.json and shows:
- Which ID the buddy system is actually using
- What species + rarity it produces
- Whether
accountUuidis overridinguserID
# Find a legendary cat (default: 500k attempts)
node reroll.js cat
# Find a legendary dragon (more attempts for safety)
node reroll.js dragon 2000000
# Find any legendary (try all species)
for s in duck goose blob cat dragon octopus owl penguin turtle snail ghost axolotl capybara cactus robot rabbit mushroom chonk; do
node reroll.js $s 100000 &
done
waitThe script outputs the best ID found:
Searching for legendary cat (mode: hex, max: 500,000)...
found: epic cat -> 6a680941c1fd99006b06e27ba9966f574d46165b2fb13f5a88fb3d7474617e23
found: legendary cat -> da55a6e264a84bb4ab5e68f09dd9f6b096f1394a758d1d3ad603f706cab71bcf
Best: legendary cat -> da55a6e264a84bb4ab5e68f09dd9f6b096f1394a758d1d3ad603f706cab71bcf
node verify.js da55a6e264a84bb4ab5e68f09dd9f6b096f1394a758d1d3ad603f706cab71bcfEdit ~/.claude.json:
If you're a free user:
{
"userID": "da55a6e264a84bb4ab5e68f09dd9f6b096f1394a758d1d3ad603f706cab71bcf"
}If you're on a Team/Pro plan:
{
"oauthAccount": {
"emailAddress": "you@company.com",
"organizationUuid": "...",
"organizationName": "Your Team Plan"
},
"userID": "da55a6e264a84bb4ab5e68f09dd9f6b096f1394a758d1d3ad603f706cab71bcf"
}Note: accountUuid is removed from oauthAccount. Everything else stays.
Also delete the companion field entirely (if it exists) to force a fresh hatch.
- Quit Claude Code
- Relaunch Claude Code
- Run
/buddy - Enjoy your new legendary companion
If you're using the shell alias (Method 1), you don't need to do anything — recovery is automatic. The alias strips accountUuid on every launch.
If you're not using the alias and a re-login overwrites your config, just run:
bash fix.shThen restart Claude Code and /buddy again. Your userID persists across re-logins, so you'll get the same species + rarity back (the AI-generated name/personality will be new).
| File | Purpose |
|---|---|
reroll.js |
Brute-force search for a target species + rarity |
shiny_hunt.js |
Deep search with full cosmetics — eye, hat, shiny, stats |
verify.js |
Check what buddy any ID produces, or auto-read config |
fix.sh |
One-command recovery after a forced re-login |
The following is the complete investigation that led to these findings. This started as a simple "give me a legendary cat" and turned into a deep dive into Claude Code internals.
A script circulating on GitHub claims you can brute-force a userID and write it to ~/.claude.json:
node reroll.js cat 500000
# found: legendary cat -> da55a6e264a84bb4ab5e68f09dd9f6b096f1394a758d1d3ad603f706cab71bcfWe set the userID in ~/.claude.json and deleted the companion field. After restarting and running /buddy... we got an epic cactus named Spindle. Not a legendary cat.
Why it failed: The script only accounts for userID, but Team/Pro plan users have an oauthAccount.accountUuid that takes priority.
We dug into the Claude Code source (cli.js, minified) and found:
function ch1() {
let q = w8();
return q.oauthAccount?.accountUuid ?? q.userID ?? "anon";
}The identity resolution order:
oauthAccount.accountUuid(if logged in)userID(fallback)"anon"(last resort)
Our real accountUuid (bda1327f-...) produces epic cactus — explaining why we kept getting Spindle regardless of what userID was set to.
We brute-forced a UUID that produces legendary cat:
# found: legendary cat -> 5fcd2193-2d37-4c7d-8ef3-0bf369735333But changing accountUuid risks breaking Team Plan access, since the server validates the UUID against your OAuth session. A re-login would overwrite it anyway.
The key insight: the ?? (nullish coalescing) operator falls through on undefined. If accountUuid doesn't exist as a field, the expression evaluates to userID instead.
// oauthAccount exists, but accountUuid is undefined
config.oauthAccount?.accountUuid // → undefined
?? config.userID // → "da55a6..." (our legendary cat ID)
?? "anon"By deleting only the accountUuid field while keeping the rest of oauthAccount intact:
- Buddy system falls back to
userID→ legendary cat - Auth continues working (uses OAuth tokens, not UUID)
- Team Plan stays active
We verified from the deobfuscated source that bones (rarity, species, stats) are never stored — they're regenerated from the identity hash on every read. The companion field in config only stores name, personality, and hatchedAt.
This means:
- There is no evolution, XP, or leveling system
- Stats are fixed (deterministic from your identity)
- If your identity changes, your species/rarity changes instantly
- The companion reacts to your code via an API call (
buddy_react), but this doesn't affect stats
All findings are based on the deobfuscated Claude Code source:
- Identity resolution:
companionUserId()inbuddy/companion.ts - Bone generation:
roll()→rollFrom()→rollRarity()+pick(SPECIES)inbuddy/companion.ts - What's stored:
StoredCompanion = { name, personality, hatchedAt }inbuddy/types.ts - What's regenerated:
CompanionBones = { rarity, species, eye, hat, shiny, stats }inbuddy/types.ts - Hatching:
FRY()writes only{ name, personality, hatchedAt }to config - Reading:
getCompanion()merges stored soul + regenerated bones
The personality field is one of the three things actually stored in your config (along with name and hatchedAt). It's sent to the buddy_react API on every reaction call, so the server uses it as context when generating your buddy's speech bubble responses.
This means you can edit it to change how your buddy talks — no restart needed. The config is read live.
By default, buddies speak English. To make yours speak another language, edit ~/.claude.json:
{
"companion": {
"name": "Hubrikat",
"personality": "一隻串到冇朋友嘅貓,成日用貓嘅視角睇唔起人類寫嘅code。Must always respond in Cantonese 廣東話. Never use English.",
"hatchedAt": 1775070893718
}
}The instruction at the end (Must always respond in...) guides the API's language choice. Not 100% guaranteed, but works most of the time.
You can write any personality you want. Keep it under 200 characters — the API truncates at .slice(0, 200). Some ideas:
# Snarky code reviewer
"A mass-produced mass that mass-critiques your mass of code. Blunt, brief, brutal."
# Encouraging cheerleader
"A sunshine-filled cat that celebrates every git commit like it's a moon landing."
# LIHKG-style troll cat (Cantonese)
"連登仔貓,串到冇朋友,短、毒、到肉。Must always respond in Cantonese 廣東話. Never use English."
# Pirate
"A salty sea-cat who speaks only in pirate dialect and judges your code like a stolen treasure map."
| Field | Editable? | Effect | Needs restart? |
|---|---|---|---|
personality |
Yes | Changes how buddy talks | No — read live |
name |
Yes | Changes display name | No — read live |
rarity |
No | Regenerated from identity hash | N/A |
species |
No | Regenerated from identity hash | N/A |
eye, hat, shiny |
No | Regenerated from identity hash | N/A |
stats |
No | Regenerated from identity hash | N/A |
Q: Will my buddy evolve or level up? A: No. There is no progression system. Stats are fixed by your identity hash. The buddy reacts to your code contextually (via API), but nothing changes permanently.
Q: Can I just edit the rarity in ~/.claude.json?
A: No. Bones (rarity, species, stats) are regenerated from your identity on every read. The source code comment explicitly says: "users can't edit their way to a legendary."
Q: Will this survive Claude Code updates?
A: The userID in your config persists across updates. However, if Anthropic changes the salt (friend-2026-401) or the algorithm, all buddy rolls will change. You'd need to reroll with the new parameters.
Q: Will /buddy pet do anything special?
A: It triggers an animation and a reaction from the buddy. No permanent effect.
Q: What if I run on Bun instead of Node?
A: Bun uses a different hash function (Bun.hash() instead of FNV-1a). The reroll scripts use the Node FNV-1a implementation. If Claude Code runs under Bun on your machine, the rolls may differ. Check with node verify.js auto after applying.
Q: How rare is a shiny? A: 1% chance, rolled after species and hat. Shiny status is also regenerated from identity (not stored), so you can't fake it.
MIT
