Skip to content

status.json can be overwritten with a companion that isn't the active one in the menagerie #163

Description

@ramarivera

Symptom

Twice in two days, the rendered companion silently changed — the statusline showed Daffodil (a common duck) while the menagerie's active companion was still Cobalt (uncommon pikachu, wizard hat). The personality text stayed Cobalt's, which is what makes it read as "cursed": the pet's body changes but its voice doesn't.

status.json     ->  Daffodil duck
menagerie.json  ->  active: buddy  ->  Cobalt pikachu

Cause

status.json is a render cache derived from the menagerie, but nothing enforces that relationship. Any process that calls writeStatusState against the state dir can put an arbitrary companion in it, and the statusline will faithfully render whatever it finds.

In practice this happened when development/test processes ran ad-hoc commands against the real state dir (~/.config/claude/buddy-state) instead of a temporary one. Both times buddy_show restored it, because the menagerie was never corrupted — only the cache.

Why it matters beyond development

Any code path that hatches or renders a companion without going through the active-companion lookup can desync the cache. The user sees their pet replaced with no explanation and no way to tell whether the underlying companion was lost. It reads like data loss even though nothing was lost.

Proposed fix

Make the invariant explicit rather than assumed:

  1. writeStatusState should verify the companion it is about to persist matches the menagerie's active companion, and refuse (or log loudly) on mismatch.
  2. On read, if status.json disagrees with the menagerie's active companion, treat the menagerie as authoritative and rebuild the cache rather than rendering the stale/foreign entry.
  3. Add a regression test: write a foreign companion into status.json, render, and assert the active companion is shown.

Optionally: a guard in the test/CI fixture helpers that refuses to write when the target resolves to the user's real state dir, so this class of accident cannot recur during development.

🤖 This content was generated with AI assistance using Claude Opus 5.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions