#What it does
A frameless, truly transparent, always-on-top window — clicks only land on the pet's opaque pixels. Native on both platforms, zero dependencies: Swift/AppKit on macOS, Rust/GTK on Linux, sharing one pet and one XP pool.
Watches every session
One pet tails all Claude Code (~/.claude/projects/*.jsonl)
and Codex (~/.codex/sessions/**/rollout-*.jsonl) transcripts
at once, normalizing both providers to the same phases.
Needs-input detection
Dings when an agent literally asks you something —
AskUserQuestion/ExitPlanMode for Claude,
request_user_input for Codex — and catches Claude Code
permission prompts via an optional Notification hook.
Stalled sessions
A session that goes silent mid-turn (hung tool, unhooked permission prompt, crash) is flagged stalled in amber instead of quietly pretending to work — and still dings if a late turn-end arrives.
Sounds you can trust
A double ping when an agent needs you, a quiet chime when a turn finishes. Debounced, one ding per session, mutable from the panel. The false-positive budget is zero: a wrong ding is release-blocking.
Jump back to the terminal
Click a session card and the terminal running it comes forward — the exact tab in iTerm2, Terminal.app and Ghostty, matched on the session's tty or working directory. No hook, no shell integration.
XP, stages & 8 species
Earns XP from your sessions; an egg hatches at 30 XP and grows hatchling → adult → legendary. Pick from cat, dragon, crab, octopus, dino, fox, alien, or turtle in the visual sprite picker.
Live session cards
Click the pet for a panel of per-session cards: project badge,
phase-tinted border, current tool + command/file, context size
(ctx 84k), and the agent's last message snippet.
#Install
One tool, one installer — macOS and Linux, native on both. Clone + build from source:
# linux build deps: sudo apt install libgtk-3-dev (+ cargo via rustup.rs) # macos build deps: Xcode Command Line Tools (swiftc) — macOS 13+ $ git clone https://github.com/chud-lori/session-pet.git $ cd session-pet $ ./install.sh --login-item # build + run + start at every login
Or skip the clone and toolchain — downloads a prebuilt from the
latest
release (needs a published release; prebuilts only need
python3 + the GTK3 runtime already on GNOME/KDE/XFCE
desktops):
$ curl -fsSL https://raw.githubusercontent.com/chud-lori/session-pet/main/install.sh | sh
Add -s -- --login-item to start at every login.
./install.sh --uninstall removes it on either OS, leaving
your pet's state untouched. Wayland/compositor notes live in the
INSTALL.md.
Optional — permission-prompt alerts:
to get a ding on Claude Code permission prompts, merge this fragment into
~/.claude/settings.json under "hooks" yourself
(the installer never edits your settings; requires jq):
"Notification": [{"hooks": [{"type": "command", "async": true,
"command": "jq -c . >> /path/to/session-pet/.state/events.jsonl"}]}]#Using the pet
Click the pet → session panel (click again or anywhere outside to close) · drag to move it anywhere · right-click → menu (open panel / sound on-off / quit). In the panel: click a session card to jump to the terminal running it (and acknowledge its alert), or right-click it for details (path, context tokens, last message); settings ▸ holds the species picker, the sound toggle, and the wander toggle.
Wandering: the pet takes short
strolls along your screen with an animated walk cycle — never while
something needs your attention, never with the panel open, and it stays
wherever it (or you) last moved it. Sprite packs can ship hand-drawn
"walk" frames; packs without them get an automatic
two-frame leg shuffle.
Start / stop from a shell with the bundled helper — the pet also comes back automatically at next login:
$ ./pet # start — or bring it back after quitting $ ./pet stop # quit (same as right-click → Quit) $ ./pet status # is it running?
Sounds: a quiet Glass when a turn finishes; a louder double Ping when an agent needs your input — repeating every 45 s (max 3×) until acknowledged, so it cuts through whatever you're watching. Muted? The pet jumps excitedly on every alert and keeps a small reminder hop going (with the "!" badge) until acknowledged — motion works when sound can't.
#How it works
No SDKs, no daemons inside your agents — just careful reading of the transcript files the agents already write.
Tail, don't read
Every couple of seconds the pet reads only the tail of each recent transcript (last 64 KB, escalating to 512 KB when a turn spans further back), with lossy UTF-8 decoding so a stray byte never breaks parsing. Gigabyte-long sessions cost almost nothing.
Turn-end from the transcript, not the clock
A session is "done" only when its last event says so —
stop_reason: end_turn for Claude,
task_complete for Codex. A long tool run or thinking
pause never counts as done, so there are no false "ready" dings.
Stop-hook continuation detection
A Claude end_turn is not always the end of the turn:
a blocking Stop hook can continue the conversation
seconds later (one real session produced 33 such intermediate
end_turns). The pet spots the "Stop hook feedback" event that follows
and keeps the session in working — one ding, at the real end.
Permission prompts via hook
Claude Code permission prompts don't appear in the transcript, so an
optional Notification hook appends them to
.state/events.jsonl; the pet tails that file and turns
them into needs input. Sessions silent mid-turn without a
hook event go stalled after a grace period.
One shared brain
Species, name, XP, and the sound toggle live in
.state/state.json — shared with the (deprecated) Python
statusline pet, so your pet keeps its identity everywhere. Sprites
come from native/assets.json, the pixel-map source of
truth (the ones on this page are rendered straight from it).
#FAQ
What sounds does it play, and how do I mute them?
Two macOS system sounds, played via afplay:
Ping.aiff when an agent needs your input and
Glass.aiff when a turn finishes. Each has its own
debounce clock, so a needs-input ping is never swallowed by a
just-played ready ding.
Mute from the pet's panel (Sound checkbox) or the right-click menu —
the setting persists in .state/state.json.
Which environment variables does the pet understand?
SESSION_PET_HOME— override the home directory the pet scans for~/.claudeand~/.codex(used by the test suite to point at fixtures).SESSION_PET_LOG=1— enable debug logging.
How do I set up the permission-prompt hook?
Merge the "Notification" hook fragment printed at the end
of ./install.sh (also shown in the Install section above)
into ~/.claude/settings.json under "hooks".
The installer deliberately never edits your settings for you.
The hook needs jq (brew install jq). It runs
async and appends one JSON line per notification to
.state/events.jsonl; the pet truncates that log itself so
it never grows unbounded. Without the hook everything else still works —
permission prompts just surface as stalled after a grace period
instead of an instant ping.
Does it work with both Claude Code and Codex?
Yes — that's the point. Both providers are parsed by per-provider tailers and normalized to the same phases, so one pet (and one panel) covers every session on the machine. Deliberately only those two: no 17-provider support, ever.
How do I uninstall it?
./install.sh --uninstall unloads and deletes the
LaunchAgent (~/Library/LaunchAgents/com.session-pet.plist)
and stops the running pet. The repo and your pet's state are left
untouched — delete the cloned directory if you want it all gone.
If you added the Notification hook, remove that fragment from
~/.claude/settings.json too.
Can the pet approve permission prompts or chat with the agent?
No, by design. The pet notifies; you act in the terminal. In-pet approve/deny, chat-with-agent, and menu-bar rewrites are explicit non-goals — it stays a small floating pet that never lies about session state.