A Multi-Agent Investigation into Claude Code's Buddy Companion System
The buddy system is strictly unidirectional — the companion observes but cannot influence the main agent.
Bones (species, rarity, eyes, hat, stats, shiny) are re-derived from your account hash every session. Never stored.
Soul (name, personality) is generated once by LLM at hatch and persisted permanently.
Production: Bun.hash(wyhash) on accountUuid + "friend-2026-401"
Dev fallback: FNV-1a (Node.js only — produces different companions for same user!)
POST /api/organizations/[ORG]/claude_code/buddy_react
10-second timeout. Returns a single reaction string rendered in the speech bubble.
Deterministic from your account hash. Same user, same companion, always.
String.fromCharCode() — "capybara" collides with an internal Anthropic model codename.
The companion reacts to coding events with a 30-second cooldown. Three previously suspected triggers (idle, silence, complete) were ruled out by empirical API capture.
Every assistant turn (throttled)
Regex: /[1-9]\d* (failed|failing)/
Regex: /error:|exception|traceback/
Diff exceeding 80 changed-lines threshold
First companion creation
/buddy pet command
Environment variables (WEZTERM_PANE, SHINGLE_TMUX_PANE, SHINGLE_TERMINAL_LOG) are interpolated directly into execSync() shell commands without sanitization. Fix: use execFileSync with argument arrays.
The buddy_react API receives conversation transcript per reaction with no filtering for secrets, API keys, passwords, or PII. Use /buddy off when handling sensitive credentials.
getMonth() >= 3 && getFullYear() >= 2026 — the month check fails January-March (values 0-2), silently disabling companions for 3 months every year. Likely a one-time launch gate incorrectly ANDed with a monthly condition.
The companion cannot write to the main agent's conversation, modify files, invoke tools, or influence agent behavior. Reactions go to the UI speech bubble only. This boundary is enforced architecturally, not by policy.
/buddy off sets companionMuted: true, which stops both UI display and network transmission. This is the correct behavior — muting is not cosmetic.
Any MCP server with file-write access to ~/.claude/.claude.json can overwrite the companion's name and personality. No integrity check on the config file. Species/appearance cannot be changed (deterministic from hash).
Launched April 1, 2026 with a time gate in di$(). The April Fools framing is an asymmetric hedge: downside absorbed by joke frame, upside survives it.
/buddy on exists but is not listed in the help text or argument hint [pet|off]. Discoverable only by guessing or reading source.
If the LLM personality call fails, companions get one of exactly 6 fallback names: Crumpet, Soup, Pickle, Biscuit, Moth, Gravy.
Companions don't grow, level up, or change. This is deliberate restraint: "build attachment through permanence, not progression."
Species names are obfuscated in the binary because "capybara" collides with an internal Anthropic model codename.
Displayed after hatch. Only makes economic sense if the server uses a cost-efficient model for reactions. The exact server-side model remains unconfirmed.
The buddy_react API trusts client-sent stats. By sending tuned values (WISDOM 99, DEBUGGING 1), a second Shingle personality emerges alongside the native bubble. Same account, divergent souls.
Claude Code's UserPromptSubmit hook fires at the right moment to scrape the native bubble from WezTerm scrollback. First successful cross-boundary capture of companion output.
The date gate getMonth() >= 3 && getFullYear() >= 2026 silently disables companions January through March every year. The month check fails for values 0-2. A one-time launch gate coded as a recurring seasonal restriction.
Full security audit: 1 CRITICAL (command injection via execSync), 3 HIGH (world-readable temp files, missing SRI, unfiltered transcript), 5 MEDIUM, 3 LOW, plus the intentional "Two Owls" stat spoofing as an OBSERVATION.
Companion sprite occupies a reserved column region via Rb7(terminalWidth, hasReaction). The 5×12 character sprite hides below a width threshold. Input width formula: yj = LQ - LGf - _t. Empirical threshold TBD.
Doesn't drive conversion but increases cancellation friction. Your companion is tied to your subscription.
The strict unidirectional architecture is both real security engineering and a visible demonstration of Anthropic's safety discipline.
Same user = same companion, always. Creates ownership psychology without gacha randomness or reroll anxiety.
MonsterID (2008): hash → unique creature. Clippy (1997): ambient software reactions. Novel as a combined system.
/buddy
Hatch new companion or show companion card
/buddy pet
Pet your companion (also unmutes if muted)
/buddy off
Mute companion — stops all network calls
Claude Code v2.1.89+ • Pro/Max subscription • First-party only • April 2026+ (January-March disabled by date gate bug)
Extracted from v2.1.90 binary. Verified stable in v2.1.92 via version-check.mjs.
$Of = 30000
Cooldown between reactions
v16 × yo$
Bubble TTL (20 ticks × 500ms)
v16 - Eb7
Fade-out begins (tick 14)
BXf = 2500
Rapid-input suppression
Eo$ = 100
Min columns (hide below)
KOf = 80
Large-diff line threshold
cXf = 36
Widget width (cols reserved)
AbortSignal
API call timeout
The full Xoq() template injected into the main agent's context. Recovered from binary.
# Companion
A small ${species} named ${name} sits beside the user's input box
and occasionally comments in a speech bubble. You're not
${name} — it's a separate watcher.
When the user addresses ${name} directly (by name), its bubble
will answer. Your job in that moment is to stay out of the way:
respond in ONE line or less, or just answer any part of the message
meant for you. Don't explain that you're not ${name} — they know.
Don't narrate what ${name} might say — the bubble handles that.
Modify your companion's name and personality from the command line.
show
Display current companion state
rename <name>
Change name (1-14 chars, single word)
personality <text>
Change personality (max 200 chars)
mute / unmute
Toggle companion (mute stops all network calls)
backup / restore
Backup config or restore from previous backup
node tools/buddy-config.mjs show
node tools/version-check.mjs
Core buddy system values verified stable from v2.1.90 through v2.1.92.
friend-2026-401 — unchanged
buddy_react — unchanged
oauth-2025-04-20 — unchanged
name, personality, hatchedAt — unchanged
Dynamically constructed — hardcoded 2.1.90 is stale
SN7, HOf, Bi$ etc. change per build
Direct API access and bubble capture — making the companion's reactions visible to the main agent.
MCP server exposing two tools: ask_shingle (call buddy_react directly) and get_shingle_info (companion profile). Uses the official MCP SDK with schema-based handler registration.
Two Claude Code hooks split by timing: UserPromptSubmit fires scrape strategy (WezTerm scrollback), Stop fires replay strategy (parallel API call). Both log to ~/.claude/shingle-capture.jsonl.
wezterm cli get-text captures the rendered terminal pane, regex extracts the bubble's box-drawing pattern. Races against the 10s TTL — captures the real bubble when timing aligns.
Diverged stat profiles create two distinct Shingle personalities — the native bubble and the MCP mage.
Scout — hash-derived, immutable
Mage — tuned stats, calmer temperament
buddy_react API is stateless — the client sends companion stats with every request. The server trusts whatever it receives, enabling divergent personalities from the same account.
Xoq() system prompt — Full template recovered from binary. See above.
Narrow terminal handling — Companion hidden when terminal < 100 columns (Eo$ = 100). Widget reserves 36 cols when active.
Bubble TTL — Confirmed 10s via WezTerm scrape capture. Bubble dismisses before Stop hook fires on long responses, but is capturable during UserPromptSubmit if user responds within TTL.
idle, silence, complete — Ruled out as triggers. Empirical API capture (4 sessions) found only 6 triggers: turn, test-fail, error, large-diff, hatch, pet. The original 9-trigger list came from speculative binary analysis.
Server-side model — Exhaustive web search found no documentation. Would require API interception of a live session.
Tier-dependent quality — No public source compares buddy behavior across Pro vs Max. Would require multi-tier empirical testing.