A sidecar reviewer agent for Claude Code and Codex. Splits your tmux window, runs the other agent in the new pane, and forwards each user/assistant exchange from the host to the reviewer via a Claude Code Stop hook. The reviewer sees the discussion in near real time; by convention it stays quiet and only replies when the user asks it to.
Named after the Yiddish kibbitzer — the spectator who can't resist offering an opinion.
kibitz startsplits the current tmux window (vertical divider), launches the reviewer agent in the new pane, labels the pane via smux (codexorclaude-reviewer).- A Claude Code
Stophook extracts the latest user + assistant turn from the session transcript and forwards them to the reviewer pane usingtmux-bridge. - The reviewer sends messages back two ways, both prepend a
[kibitz from:<agent>]header so the host can tell them apart from real user input:kibitz send "<text>"— send custom text.kibitz relay— forward the agent's own last assistant reply verbatim to its counterpart pane. From a codex reviewer pane, forwards codex's reply to the claude host; from a claude host pane, forwards claude's reply to the codex reviewer. Each agent'sStophook stasheslast_assistant_messageunder$XDG_CACHE_HOME/kibitz/—codex-<thread>.msgkeyed onCODEX_THREAD_ID,claude-<pane>.msgkeyed on the host pane's$TMUX_PANE— andrelayreads the relevant file and forwards it. No dedupe — runningrelaytwice on the same turn sends twice.
kibitz stop/kibitz restart/kibitz statusmanage the pane lifecycle.kibitz uninstalltears everything down cleanly.
- Reviewer is read-only by default. The sidecar is meant to watch and comment, not drive. It should only message the host back when the user explicitly asks it to relay something.
- Host can tell who typed what. Anything that arrives at the host with a
[kibitz from:<agent>]header came from the reviewer (emitted bykibitz send). Anything without it is real user input. - Slash commands are not forwarded. If the most recent user turn starts with
/(like/clear), the Stop hook skips forwarding that exchange. - Recursion-safe. If the hook runs inside a pane already labeled
codexorclaude-reviewer, it no-ops — so nesting sessions doesn't create a forwarding loop.
Append one of these tokens to the end of your message to change what the reviewer sees for that turn. Tokens are case-sensitive and must be preceded by whitespace (or stand alone as the whole message). Non-trailing occurrences are ignored.
| You type | Submit-time | After Claude's reply |
|---|---|---|
hello |
— | forward USER + CLAUDE |
hello /mute |
— | nothing |
hello /tee |
forward USER (stripped) | nothing |
/mute (bare) |
— | nothing |
/tee (bare) |
nothing | nothing |
/mute— skip forwarding this exchange entirely. Useful for side chatter you don't want the reviewer to spend context on./tee— send your message to the reviewer immediately (via aUserPromptSubmithook), and don't forward Claude's reply. Useful when you want the reviewer to work on the same problem in parallel rather than critique the answer. If the submit-time forward fails (transport glitch, reviewer pane gone), the turn isn't silently retried at Stop time — errors land in~/.claude/kibitz-hook.logand you can resend with/teemanually.
Claude itself still sees the /mute or /tee suffix in your prompt — Claude Code hooks can observe a prompt but can't rewrite it in place. Treat the trailing directive as benign noise; the model ignores it.
- Linux (host detection reads
/proc; macOS fallback is explicit-arg-only) - tmux 3.2+
python3on PATHcurlorwget(for install)- One of
codexorclaudeCLIs on PATH for whichever agents you want to run
One-liner (no checkout needed):
curl -fsSL https://raw.githubusercontent.com/balvisio/kibitz/main/kibitz | bash -s installOr equivalently:
bash <(curl -fsSL https://raw.githubusercontent.com/balvisio/kibitz/main/kibitz) installWhen invoked without a local file on disk, install fetches the repo tarball into a tempdir and runs from there. Override the source with KIBITZ_REPO / KIBITZ_BRANCH env vars.
From a checkout of this repo:
./kibitz installWhat install does:
- Verifies
python3is on PATH. - Copies
scripts/tmux-bridge,kibitz, and thehooks/kibitz_*.pyscripts (Claude hooks +kibitz_codex_stop.py) into~/.local/bin/. - Merges the Stop and UserPromptSubmit hook entries from
hooks/kibitz_hook.jsoninto~/.claude/settings.json(idempotent — won't duplicate on reinstall, preserves any other hooks you have). - Merges the codex Stop hook from
hooks/kibitz_codex_hooks.jsoninto~/.codex/hooks.json(same idempotent behavior), and sets[features] codex_hooks = truein~/.codex/config.tomlso codex actually fires hooks. Ifcodex_hooksis already set tofalsethe installer warns and leaves it alone. - Adds
~/.local/bin/toPATHin your shell rc (.zshrc/.bashrc/.profile) if it isn't already. - Warns if
codexorclaudebinaries are missing (non-fatal).
tmux-bridge is vendored from ShawnPana/smux under scripts/.
After install, open a new shell (or source your rc) so PATH picks up the new entries.
All commands work whether you invoke ./kibitz from the repo or kibitz from PATH after install.
# Start a sidecar. Auto-detects the host agent and launches the other one.
# Explicit override: `kibitz start codex` or `kibitz start claude`.
kibitz start
# Stop the sidecar (kills the labeled pane).
kibitz stop
# Show whether a sidecar is running, and where.
kibitz status
# Stop + start, optionally switching agents.
kibitz restart claude
# Forward the agent's own last reply verbatim to the counterpart pane:
# codex reviewer pane → claude host (header: [kibitz from:codex])
# claude host pane → codex reviewer (header: [kibitz from:claude])
# Fails if no turn has been stashed yet. Running it twice sends
# twice — there's no dedupe.
kibitz relay
# Remove the Stop hook entries and delete all installed scripts.
kibitz uninstalltmux-bridge is the communication layer. Labels are codex or claude-reviewer depending on which agent is running.
tmux-bridge read codex 30 # read last 30 lines of the reviewer pane
tmux-bridge type codex "summarize" # type text into it (no Enter)
tmux-bridge keys codex Enter # press Enter in the reviewer panekibitz/
├── kibitz # the management script (bash)
├── hooks/
│ ├── kibitz_hook_stop.py # Claude Code Stop hook — forwards user/assistant exchange, stashes last_assistant_message for `kibitz relay`
│ ├── kibitz_hook_user_prompt_submit.py # Claude Code UserPromptSubmit hook — implements `/tee`
│ ├── kibitz_hook_common.py # shared helpers imported by both Claude hooks
│ ├── kibitz_hook.json # settings.json fragment `install` merges in
│ ├── kibitz_codex_stop.py # Codex Stop hook — stashes last_assistant_message for `kibitz relay`
│ └── kibitz_codex_hooks.json # ~/.codex/hooks.json fragment `install` merges in
├── scripts/
│ └── tmux-bridge # vendored from ShawnPana/smux
├── LICENSE
└── README.md
After install, the runtime copies land in ~/.local/bin/:
~/.local/bin/
├── kibitz
├── kibitz_hook_stop.py
├── kibitz_hook_user_prompt_submit.py
├── kibitz_hook_common.py
├── kibitz_codex_stop.py
└── tmux-bridge
And hook entries are added to ~/.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.local/bin/kibitz_hook_stop.py" }
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.local/bin/kibitz_hook_user_prompt_submit.py" }
]
}
]
}
}And to ~/.codex/hooks.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "$HOME/.local/bin/kibitz_codex_stop.py" }
]
}
]
}
}Plus [features] codex_hooks = true in ~/.codex/config.toml (codex won't fire hooks without it).
At runtime both Stop hooks write relay payloads under $XDG_CACHE_HOME/kibitz/ (falls back to ~/.cache/kibitz/): the codex hook writes codex-<thread>.msg keyed on CODEX_THREAD_ID, and the Claude hook writes claude-<pane>.msg keyed on the host pane's $TMUX_PANE (with the leading % stripped). Each file is overwritten atomically at the end of each turn. Errors from the codex hook land in $XDG_CACHE_HOME/kibitz/log; errors from the Claude hook land in ~/.claude/kibitz-hook.log.
tmux-bridge not found— runkibitz install, or check that~/.local/binis onPATH.- Hook not forwarding (Claude side) — check
~/.claude/kibitz-hook.log. The hook always exits 0 (so it can't block your session), and routes errors there. kibitz relaysaysno relay payload— the relevant Stop hook hasn't written a stash for this pane/thread yet. From a codex reviewer pane: check$XDG_CACHE_HOME/kibitz/logfor codex Stop hook errors and confirm[features] codex_hooks = truein~/.codex/config.toml— without it codex won't fire hooks at all. From a claude host pane: check~/.claude/kibitz-hook.log; the stash runs inside the same Stop hook that forwards exchanges, so if one is broken both are.kibitz relaysaysCODEX_THREAD_ID is not set— only the codex-reviewer path requires it; the claude-host path keys on$TMUX_PANE. You're likely running relay from a codex-labeled pane where codex didn't inherit the env var (unusual — it's normally exported automatically).couldn't detect host agent— host detection is Linux-only (/proc/$PPID/exe). On macOS, or when running through an unusual wrapper, pass the agent explicitly:kibitz start codex.- Stale reviewer pane — if
kibitz statusreports a pane that no longer exists, something killed the pane outside of kibitz. Runkibitz stopto clear the label, thenkibitz start.
kibitz uninstallRemoves the kibitz hook entries from ~/.claude/settings.json and ~/.codex/hooks.json (preserving any unrelated hooks you added), deletes kibitz, the hook scripts, and tmux-bridge from ~/.local/bin/, and clears $XDG_CACHE_HOME/kibitz/ (stashed codex payloads and hook log). Leaves [features] codex_hooks = true in ~/.codex/config.toml alone — the flag is harmless with no hooks registered, and disabling it would silently break other hooks you may have added independently. Leaves PATH edits in your shell rc alone.