
Webup Buddy Reroll
- 382 installs
- 118 repo stars
- Updated July 8, 2026
- webup/skills-cc
webup-buddy-reroll is a Claude Code skill with three Bun scripts that brute-force a ~/.claude.json userID hash to reroll /buddy companion species and rarity or rename personality for developers customizing Claude Code 2.
About
webup-buddy-reroll is a webup/skills-cc skill that rerolls Claude Code /buddy companions by brute-forcing a userID whose Bun.hash (xxHash64) seed yields a target species and rarity tier. Three scripts ship in scripts/: reroll.mjs (species+rarity search up to 1,000,000 iterations), oauth-setup.mjs (Pro/Max OAuth bypass), and rename.mjs (name and personality). Choose among 18 species (duck, dragon, axolotl, capybara, etc.) and 5 rarity tiers from common (60%) to legendary (1%, ~1800 iterations). Requires Claude Code >= 2.1.89, Bun via npx, and writes to ~/.claude.json with companion cleared before restart. Pro/Max subscribers run an OAuth workflow so accountUuid does not lock the companion seed. Invoke on reroll buddy, change companion, or rename buddy requests.
- webup-buddy-reroll
Webup Buddy Reroll by the numbers
- 382 all-time installs (skills.sh)
- +7 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,099 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/webup/skills-cc --skill webup-buddy-rerollAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 382 |
|---|---|
| repo stars | ★ 118 |
| Last updated | July 8, 2026 |
| Repository | webup/skills-cc ↗ |
How do you reroll Claude Code buddy companions?
Use webup-buddy-reroll for development tasks
Who is it for?
Claude Code 2.1.89+ users customizing /buddy companion species, rarity tier, name, or personality via deterministic hash rerolling.
Skip if: Skip webup-buddy-reroll for Cursor or Codex agents without Claude Code /buddy, or when companion customization is irrelevant to the development task.
When should I use this skill?
User asks to reroll buddy, change companion species or rarity, rename buddy, or mentions legendary buddy, buddy hack, or OAuth setup for Pro/Max accounts.
What you get
Updated ~/.claude.json userID, cleared companion field, and hatched /buddy matching target species, rarity, name, and personality after restart.
- Updated ~/.claude.json userID
- Renamed companion metadata
By the numbers
- 18 companion species and 5 rarity tiers supported
- Three bundled scripts: reroll.mjs, oauth-setup.mjs, and rename.mjs
- Requires Claude Code >= 2.1.89; default max 1,000,000 brute-force iterations
Files
Buddy Reroll
Brute-force a new /buddy companion by finding a userID that hashes to the desired species and rarity.
How It Works
Claude Code generates companions deterministically: hash(userID + SALT) → mulberry32 PRNG → rarity, species, eyes, hat, shiny. Same userID = same companion, always.
To reroll: replace userID in ~/.claude.json, clear companion field, restart.
Critical: Claude Code is Bun-bundled, so Bun.hash() (xxHash64) is the correct hash function. Node.js FNV-1a produces wrong results — the script MUST run under Bun.
Script Directory
Important: All scripts are located in the scripts/ subdirectory of this skill.
Agent Execution Instructions: 1. Determine this SKILL.md file's directory path as SKILL_DIR 2. Script path = ${SKILL_DIR}/scripts/<script-name>.mjs 3. Replace all ${SKILL_DIR} in this document with the actual path
Script Reference:
| Script | Purpose |
|---|---|
scripts/reroll.mjs | Brute-force target species+rarity, optionally apply to config |
scripts/oauth-setup.mjs | OAuth config helper for Pro/Max subscribers (--check/--prepare/--verify/--restore) |
scripts/rename.mjs | Change companion name and/or personality (--name/--desc) |
Prerequisites
- Claude Code >= 2.1.89 — the
/buddysystem was introduced in this version. The script auto-checks and exits if the version is too old. - Bun — required for correct hashing. Use
npx -y bunif not installed globally. - API users: Works directly —
userIDin~/.claude.jsoncan be freely replaced. - Pro/Max subscribers: Requires OAuth setup first (automated by this skill). Pro/Max accounts have
accountUuidwhich/buddyuses instead ofuserID. The OAuth method bypasses this — see workflow below.
Usage
# Direct (if bun is installed globally)
bun ${SKILL_DIR}/scripts/reroll.mjs --species dragon --rarity legendary --apply
# Via npx (works even without global bun install)
npx -y bun ${SKILL_DIR}/scripts/reroll.mjs --species dragon --rarity legendary --applyOptions
| Flag | Default | Description |
|---|---|---|
--species <name> | dragon | Target species (see list below) |
--rarity <level> | legendary | Minimum rarity: common / uncommon / rare / epic / legendary |
--max <n> | 1000000 | Max brute-force iterations |
--apply | off | Auto-write result to ~/.claude.json and clear companion |
Species (18)
| duck | goose | blob | cat | dragon | octopus |
| owl | penguin | turtle | snail | ghost | axolotl |
| capybara | cactus | robot | rabbit | mushroom | chonk |
Rarity Tiers
| Rarity | Stars | Chance | ~Iterations to find |
|---|---|---|---|
| common | ★ | 60% | ~30 |
| uncommon | ★★ | 25% | ~72 |
| rare | ★★★ | 10% | ~180 |
| epic | ★★★★ | 4% | ~450 |
| legendary | ★★★★★ | 1% | ~1800 |
Invocation
This skill can be invoked with or without arguments:
- No args (
/webup-buddy-reroll): Prompts the user interactively viaAskUserQuestionbefore running. - Reroll only (
/webup-buddy-reroll dragon legendary): Skips the prompt and locks to the given species and rarity. - Reroll + rename (
/webup-buddy-reroll dragon legendary 沧海九粟 爱打盹的小龙): Reroll, then immediately apply the given name and personality. - Rename only (
/webup-buddy-reroll 改名叫沧海九粟,个性是爱打盹的小龙): Skip reroll, only change name and/or personality.
Arg parsing (natural language)
The args string is free-form text. Use NLP to extract these fields:
1. species — match against the 18 known species names (duck, goose, blob, cat, dragon, octopus, owl, penguin, turtle, snail, ghost, axolotl, capybara, cactus, robot, rabbit, mushroom, chonk). Also recognize Chinese/Japanese equivalents (龙=dragon, 猫=cat, etc.). 2. rarity — match against the 5 tiers (common, uncommon, rare, epic, legendary) or equivalents (传说=legendary, 史诗=epic, etc.). 3. name — a short proper name for the companion (e.g. "沧海九粟", "Nimbus", "小火"). Look for patterns like "叫X", "名字X", "named X", "name X", or a standalone proper noun that isn't a species/rarity keyword. 4. personality — a description of the companion's vibe or character (e.g. "爱打盹的小龙", "sarcastic robot"). Look for patterns like "个性X", "性格X", "personality X", or descriptive phrases that aren't species/rarity/name.
All four fields are optional. Any combination is valid.
Detect intent:
- If species or rarity found → Reroll Workflow (+ post-reroll rename if name/personality also found)
- If only name and/or personality found (no species/rarity) → Rename Workflow
- If no args at all → prompt interactively, detect from user's message whether they want reroll or rename
Reroll Workflow
0. Detect account type: Run the check script to see if accountUuid exists in ~/.claude.json:
npx -y bun ${SKILL_DIR}/scripts/oauth-setup.mjs --check- Exit 0 (no
accountUuid): API user or OAuth already set up. Skip to step 1. - Exit 1 (
accountUuidfound): Pro/Max subscriber. Run OAuth Setup below, then continue to step 1.
OAuth Setup (Pro/Max only)
Principle: When logging in via CLAUDE_CODE_OAUTH_TOKEN env var, Claude Code does NOT write accountUuid to ~/.claude.json. This makes /buddy fall back to userID, which can be freely replaced.
a. Tell user to open a separate terminal and run claude setup-token to get their OAuth token. Ask them to paste the token back here.
b. Once token is received, prepare the config (backup + reset):
npx -y bun ${SKILL_DIR}/scripts/oauth-setup.mjs --preparec. Tell user to run in a separate terminal (replace <TOKEN> with actual token):
CLAUDE_CODE_OAUTH_TOKEN=<TOKEN> claudeWait for Claude to fully load, then exit immediately — do NOT use /buddy.
d. After user confirms they've done this, verify the config:
npx -y bun ${SKILL_DIR}/scripts/oauth-setup.mjs --verifyIf verification fails, run --restore to restore backup and retry from step (a).
e. Continue to step 1.
---
1. If no args provided: Use AskUserQuestion to ask the user to pick a species AND a rarity in a single prompt (two questions):
- Species question: Present 4 popular options (dragon, cat, axolotl, capybara) as choices. Each option's
descriptionshould list the remaining species so the user knows what's available via "Other", e.g. the first option's description: "Or type any of: duck, goose, blob, octopus, owl, penguin, turtle, snail, ghost, cactus, robot, rabbit, mushroom, chonk". - Rarity question: Present all 5 tiers (legendary as first/recommended, then epic, rare, uncommon, common).
- Do NOT skip this step. Always ask even if the user mentioned a species — confirm their choice.
If species/rarity parsed from args: Skip the prompt and proceed directly. 2. Run the script with --apply:
npx -y bun ${SKILL_DIR}/scripts/reroll.mjs --species <choice> --rarity <choice> --apply3. If name or personality was parsed from args: Run the rename script immediately after reroll (no restart needed between):
npx -y bun ${SKILL_DIR}/scripts/rename.mjs --name "<parsed name>" --desc "<parsed personality>"Omit --name or --desc if only one was parsed. 4. Tell user to restart Claude Code and run /buddy to hatch the new companion.
- If OAuth was used: Remind user to start Claude with
CLAUDE_CODE_OAUTH_TOKEN=<TOKEN> claude(not plainclaude) to preventaccountUuidfrom being written back.
Manual Apply
If --apply was not used: 1. Copy the output userID 2. Edit ~/.claude.json: replace "userID" value, set "companion": null 3. Restart Claude Code → /buddy
Rename / Customize Companion
Change the companion's name and/or personality description without rerolling species or rarity.
Rename Workflow
If name and/or personality were parsed from args: Skip steps 1–3, go directly to step 4 with the parsed values. When personality is provided directly via args, use it as-is — skip the AI generation step.
1. Use AskUserQuestion to ask what the user wants to change (one question, multiSelect):
- Name: "Change companion name"
- Personality: "Change companion personality/description"
2. For name: ask the user for the exact name. Pass it directly. 3. For personality: ask the user for a brief tip or vibe (e.g. "lazy cat that loves keyboards", "sarcastic robot"). Then you (Claude Code) generate a fun, creative personality description (1–2 sentences, matching the companion's species and the user's tip). Show the generated text to the user for confirmation before applying. 4. Run the rename script:
npx -y bun ${SKILL_DIR}/scripts/rename.mjs --name "<name>" --desc "<personality>"Omit --name or --desc if only one is being changed. 5. Tell user to restart Claude Code for changes to take effect.
Notes
userIDonly affects telemetry bucketing and buddy seed — no impact on conversations, API keys, or local config- SALT is
friend-2026-401(Claude Code 2.1.89). Update the script if a future version changes it - 1M iterations finds legendary in seconds on modern hardware
#!/usr/bin/env bun
// OAuth setup helper for Pro/Max subscribers to enable buddy reroll.
// By logging in via CLAUDE_CODE_OAUTH_TOKEN, accountUuid is NOT written
// to ~/.claude.json, so /buddy falls back to userID (which can be replaced).
//
// Usage:
// bun oauth-setup.mjs --check Check if accountUuid exists (exit 1 = needs setup)
// bun oauth-setup.mjs --prepare Backup config and reset to minimal state
// bun oauth-setup.mjs --verify Verify config has no accountUuid after OAuth login
// bun oauth-setup.mjs --restore Restore backup if something goes wrong
import { readFileSync, writeFileSync, copyFileSync, existsSync } from 'fs'
import { join } from 'path'
import { homedir } from 'os'
const configPath = join(homedir(), '.claude.json')
const backupPath = join(homedir(), '.claude.json.oauth-backup')
const args = process.argv.slice(2)
const hasFlag = (name) => args.includes(`--${name}`)
if (hasFlag('check')) {
try {
if (!existsSync(configPath)) {
console.log('No config file found. Standard reroll will work.')
process.exit(0)
}
const config = JSON.parse(readFileSync(configPath, 'utf8'))
if (config.accountUuid) {
console.log(`accountUuid found: ${config.accountUuid}`)
console.log('This is a Pro/Max account. OAuth setup required for buddy reroll.')
process.exit(1)
}
console.log('No accountUuid found. Standard reroll will work.')
process.exit(0)
} catch (e) {
// Malformed JSON or read error — no accountUuid to worry about
console.log(`Warning: could not parse ${configPath}: ${e.message}`)
console.log('Proceeding with standard reroll.')
process.exit(0)
}
}
if (hasFlag('prepare')) {
if (existsSync(configPath)) {
copyFileSync(configPath, backupPath)
console.log(`Backed up: ${configPath} -> ${backupPath}`)
}
const minimalConfig = { hasCompletedOnboarding: true, theme: "dark" }
writeFileSync(configPath, JSON.stringify(minimalConfig, null, 2) + '\n')
console.log(`Reset ${configPath} to minimal config`)
console.log('accountUuid removed. Ready for OAuth login.')
}
if (hasFlag('verify')) {
try {
const config = JSON.parse(readFileSync(configPath, 'utf8'))
if (config.accountUuid) {
console.error('FAIL: accountUuid still present. OAuth setup did not work.')
console.error('Make sure you used CLAUDE_CODE_OAUTH_TOKEN env var when starting claude.')
process.exit(1)
}
if (!config.userID) {
console.error('FAIL: userID not found. Did you start claude with CLAUDE_CODE_OAUTH_TOKEN?')
process.exit(1)
}
console.log(`OK: no accountUuid, userID=${config.userID}`)
console.log('Ready for reroll!')
} catch (e) {
console.error(`Failed to read ${configPath}: ${e.message}`)
process.exit(1)
}
}
if (hasFlag('restore')) {
if (!existsSync(backupPath)) {
console.error(`No backup found at ${backupPath}`)
process.exit(1)
}
copyFileSync(backupPath, configPath)
console.log(`Restored ${configPath} from ${backupPath}`)
}
#!/usr/bin/env bun
// Change companion name and/or personality in ~/.claude.json
//
// Usage: bun rename.mjs [--name <name>] [--desc <personality>]
import { readFileSync, writeFileSync } from 'fs'
import { join } from 'path'
import { homedir } from 'os'
const args = process.argv.slice(2)
function getArg(name) {
const i = args.indexOf(`--${name}`)
return i >= 0 && i + 1 < args.length ? args[i + 1] : null
}
const name = getArg('name')
const desc = getArg('desc')
if (!name && !desc) {
console.error('Usage: bun rename.mjs [--name <name>] [--desc <personality>]')
console.error('At least one of --name or --desc is required.')
process.exit(1)
}
const configPath = join(homedir(), '.claude.json')
try {
const config = JSON.parse(readFileSync(configPath, 'utf8'))
if (!config.companion) {
console.error('No companion found in config. Run /buddy first to hatch one.')
process.exit(1)
}
if (name) {
const old = config.companion.name
config.companion.name = name
console.log(`name: "${old}" -> "${name}"`)
}
if (desc) {
const old = config.companion.personality
config.companion.personality = desc
console.log(`personality: "${old}" -> "${desc}"`)
}
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n')
console.log(`\nUpdated ${configPath}`)
console.log('Restart Claude Code for changes to take effect.')
} catch (e) {
console.error(`Failed to update ${configPath}: ${e.message}`)
process.exit(1)
}
#!/usr/bin/env bun
// Brute-force a target species + rarity for Claude Code /buddy companion.
// Requires Bun (uses Bun.hash which matches Claude Code's internal hashing).
//
// Usage: bun reroll.mjs [--species <name>] [--rarity <level>] [--max <iterations>] [--apply]
// --species Target species (default: dragon)
// --rarity Minimum rarity: common|uncommon|rare|epic|legendary (default: legendary)
// --max Max iterations (default: 1000000)
// --apply Write the found userID to ~/.claude.json and clear companion
import { randomBytes } from 'crypto'
import { readFileSync, writeFileSync } from 'fs'
import { execFileSync } from 'child_process'
import { join } from 'path'
import { homedir } from 'os'
const MIN_VERSION = '2.1.89'
const SALT = 'friend-2026-401'
function checkCCVersion() {
try {
const out = execFileSync('claude', ['--version'], { encoding: 'utf8' }).trim()
const match = out.match(/^(\d+\.\d+\.\d+)/)
if (!match) {
console.error(`Could not parse Claude Code version from: ${out}`)
process.exit(1)
}
const ver = match[1]
const [ma, mi, pa] = ver.split('.').map(Number)
const [rma, rmi, rpa] = MIN_VERSION.split('.').map(Number)
if (ma < rma || (ma === rma && mi < rmi) || (ma === rma && mi === rmi && pa < rpa)) {
console.error(`Claude Code ${ver} detected. Buddy system requires >= ${MIN_VERSION}.`)
console.error(`Please update: npm install -g @anthropic-ai/claude-code`)
process.exit(1)
}
console.log(`Claude Code ${ver} detected (>= ${MIN_VERSION} required) ✓`)
} catch (e) {
if (e.code === 'ENOENT') {
console.error(`"claude" command not found. Is Claude Code installed?`)
process.exit(1)
}
throw e
}
}
checkCCVersion()
const SPECIES = [
'duck','goose','blob','cat','dragon','octopus','owl',
'penguin','turtle','snail','ghost','axolotl','capybara','cactus',
'robot','rabbit','mushroom','chonk'
]
const RARITIES = ['common','uncommon','rare','epic','legendary']
const RARITY_WEIGHTS = { common:60, uncommon:25, rare:10, epic:4, legendary:1 }
const RARITY_RANK = { common:0, uncommon:1, rare:2, epic:3, legendary:4 }
const EYES = ['·','✦','×','◉','@','°']
const HATS = ['none','crown','tophat','beanie','wizard','party','flower','bow']
function mulberry32(seed) {
let a = seed >>> 0
return function() {
a |= 0; a = (a + 0x6d2b79f5) | 0
let t = Math.imul(a ^ (a >>> 15), 1 | a)
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
function hashString(s) {
return Number(BigInt(Bun.hash(s)) & 0xffffffffn)
}
function pick(rng, arr) { return arr[Math.floor(rng() * arr.length)] }
function rollRarity(rng) {
let roll = rng() * 100
for (const r of RARITIES) { roll -= RARITY_WEIGHTS[r]; if (roll < 0) return r }
return 'common'
}
function rollCompanion(uid) {
const rng = mulberry32(hashString(uid + SALT))
const rarity = rollRarity(rng)
const species = pick(rng, SPECIES)
const eyes = pick(rng, EYES)
const hat = pick(rng, HATS)
const shiny = rng() < 0.01
return { rarity, species, eyes, hat, shiny }
}
// Parse args
const args = process.argv.slice(2)
function getArg(name, def) {
const i = args.indexOf(`--${name}`)
return i >= 0 && i + 1 < args.length ? args[i + 1] : def
}
const hasFlag = (name) => args.includes(`--${name}`)
const targetSpecies = getArg('species', 'dragon').toLowerCase()
const targetRarity = getArg('rarity', 'legendary').toLowerCase()
const maxIter = parseInt(getArg('max', '1000000'))
const shouldApply = hasFlag('apply')
if (!SPECIES.includes(targetSpecies)) {
console.error(`Unknown species: ${targetSpecies}\nAvailable: ${SPECIES.join(', ')}`)
process.exit(1)
}
if (!RARITIES.includes(targetRarity)) {
console.error(`Unknown rarity: ${targetRarity}\nAvailable: ${RARITIES.join(', ')}`)
process.exit(1)
}
const targetRank = RARITY_RANK[targetRarity]
console.log(`Searching for ${targetRarity} ${targetSpecies} (max ${maxIter} iterations)...\n`)
let best = null
for (let i = 0; i < maxIter; i++) {
const uid = randomBytes(32).toString('hex')
const comp = rollCompanion(uid)
if (comp.species === targetSpecies && RARITY_RANK[comp.rarity] >= targetRank) {
if (!best || RARITY_RANK[comp.rarity] > RARITY_RANK[best.rarity]) {
best = { ...comp, uid }
const shinyTag = comp.shiny ? ' [SHINY]' : ''
console.log(`found: ${comp.rarity} ${comp.species} (eyes:${comp.eyes} hat:${comp.hat}${shinyTag}) -> ${uid}`)
if (comp.rarity === 'legendary') break
}
}
}
if (!best) {
console.log(`\nNo ${targetRarity}+ ${targetSpecies} found in ${maxIter} iterations. Try increasing --max.`)
process.exit(1)
}
console.log(`\nBest: ${best.rarity} ${best.species} (eyes:${best.eyes} hat:${best.hat}${best.shiny ? ' SHINY' : ''})`)
console.log(`userID: ${best.uid}`)
if (shouldApply) {
const configPath = join(homedir(), '.claude.json')
try {
const config = JSON.parse(readFileSync(configPath, 'utf8'))
const oldUid = config.userID
config.userID = best.uid
delete config.accountUuid
config.companion = null
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n')
console.log(`\nApplied to ${configPath}`)
console.log(` old userID: ${oldUid}`)
console.log(` new userID: ${best.uid}`)
console.log(` companion: cleared`)
console.log(`\nRestart Claude Code and run /buddy to hatch your new companion!`)
} catch (e) {
console.error(`\nFailed to update ${configPath}: ${e.message}`)
process.exit(1)
}
} else {
console.log(`\nTo apply, rerun with --apply or manually set userID in ~/.claude.json`)
}
Related skills
FAQ
Why does webup-buddy-reroll require Bun instead of Node.js?
webup-buddy-reroll requires Bun because Claude Code is Bun-bundled and uses Bun.hash() (xxHash64) for companion seeds. Node.js FNV-1a produces incorrect hashes, so scripts must run via bun or npx -y bun.
How many species and rarity tiers does webup-buddy-reroll support?
webup-buddy-reroll supports 18 species including dragon, cat, axolotl, and capybara, plus 5 rarity tiers from common (60%) to legendary (1%). Legendary targets typically resolve within roughly 1800 brute-force iterations.