Claude Code's companion system generates your pet's visual traits (species, rarity, eyes, hat) by hashing your user ID with a salt string (friend-2026-401), then feeding that hash into a deterministic PRNG (Mulberry32). The result is always the same pet for the same user -- it's recalculated on every launch, so you can't override it through config files.
- You pick your desired species, rarity, eyes, and hat through an interactive builder TUI (or sequential prompts on Node)
- Brute-force search finds a replacement salt string that produces your chosen pet when combined with your real user ID (simple searches take milliseconds; shiny/legendary/custom stats can take minutes)
- Binary patch replaces the salt in the Claude Code binary using an atomic rename, with a backup created first
- Auto-repair hook (optional) installs a
SessionStarthook in~/.claude/settings.jsonthat re-applies the patch after Claude Code updates
When running under Bun with a TTY (70+ columns, 18+ rows), the tool launches an interactive builder powered by OpenTUI:
- Left panel: Selection lists for species, eyes, rarity, hats, shiny, and optional stat customization
- Right panel: Live ASCII art preview with stat bar visualization that updates in real-time
- Navigation: Arrow keys scroll within a section, Tab/Enter advance to the next, Shift+Tab goes back, Esc cancels
- Confirmation: After the last field, Enter triggers a confirm step (Enter/Y to apply, Esc/N to go back)
- Disabled sections: Hat is disabled for common rarity, peak/dump are disabled unless stats are set to "Customize" -- with inline help messages explaining why
If the builder can't launch (no Bun, no TTY, terminal too small), the tool falls back to @inquirer/prompts sequential selection with a warning to install Bun.
The CLI entry point (cli.ts) starts with #!/usr/bin/env node. When it detects it's running under Node (not Bun), it attempts to re-execute itself under Bun using spawnSync with stdio: 'inherit' for full TTY passthrough. This is skipped for help and apply --silent (hook context, where speed matters). The __ANYBUDDY_NO_REEXEC=1 env var prevents infinite re-exec loops.
Claude Code uses different hash functions depending on how it was installed:
| Install Method | Runtime | Hash Function |
|---|---|---|
| Compiled binary (Linux/macOS) | Bun | Bun.hash (wyhash) |
| npm install (Windows) | Node.js | FNV-1a |
any-buddy detects which runtime your Claude Code uses and matches the correct hash function automatically.
Claude Code is a compiled Bun binary (ELF on Linux, Mach-O on macOS) or a JS bundle (cli.js on Windows via npm). The salt string "friend-2026-401" appears 3 times in the compiled binary (Linux/macOS) and once in the JS bundle (Windows).
The patch process:
- Reads the binary into a buffer
- Finds all occurrences of the old salt
- Replaces each with the new salt (always exactly 15 characters -- same length, no byte offset shifts)
- Writes to a temp file, then atomically renames it over the original
- Verifies by re-reading
- On macOS, re-signs the binary with an ad-hoc signature (
codesign --force --sign -)
The atomic rename (rename() syscall) is safe even while Claude Code is running -- the OS keeps the old inode open for any running process. The new binary takes effect on next launch.
A backup is always created at <binary-path>.anybuddy-bak before the first patch.
Finding a salt that produces your desired pet is a brute-force search over random 15-character strings. The search runs in parallel across up to 8 CPU cores, with each worker:
- Generating a random salt
- Hashing
userId + salt - Seeding a Mulberry32 PRNG with the hash
- Rolling traits in order (rarity, species, eye, hat, shiny, stats)
- Breaking early on the first mismatch (most iterations bail after checking rarity)
Expected attempts depend on trait rarity:
| Target | Expected Attempts | Typical Time |
|---|---|---|
| Common duck | ~180 | <1ms |
| Rare dragon with hat | ~8,640 | ~10ms |
| Legendary + specific hat | ~86,400 | ~100ms |
| Legendary + shiny | ~8,640,000 | ~10s |
| Legendary + shiny + peak + dump | ~172,800,000 | ~3min |
When installed, adds this to ~/.claude/settings.json:
{
"hooks": {
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "any-buddy apply --silent"
}
]
}
]
}
}On every Claude Code session start, this runs apply --silent which:
- Reads your saved salt from
~/.claude-code-any-buddy.json - Checks if the current binary already has the correct salt (fast
Buffer.indexOf) - If not (Claude updated), re-patches
- Silent mode: produces no output unless a patch was actually applied
The hook adds negligible startup time (~50ms) when no patch is needed. It defaults to No during setup -- you'll be asked.
| File | Purpose |
|---|---|
~/.claude.json |
Read-only -- your user ID is read from here |
~/.claude-code-any-buddy.json |
Stores your chosen salt and pet config |
~/.claude/settings.json |
SessionStart hook is added here (optional) |
<binary>.anybuddy-bak |
Backup of the original binary |