rmux_helper: add parent-pid-tree subcommand for tmux pane resolution#76
Conversation
Walks the caller's parent-PID chain from /proc/<pid>/stat against
`tmux list-panes -a -F '#{pane_id} #{pane_pid}'` to deterministically
find the owning tmux pane. Replaces `tmux display-message -p '#{pane_id}'`
for "which pane am I in" lookups — that primitive returns the focused
pane, not the caller's pane, and silently targets the wrong Claude
session when multiple sessions run concurrently.
Observed 2026-04-14: harden-telegram's watchdog.py reloaded the wrong
Claude session for ~45 minutes because it used display-message. The
companion chop-conventions PR (#101) fixes watchdog.py directly; this
PR makes the correct primitive callable from any tool that needs it.
Includes:
- ParentPidTree subcommand (--json, --pid, --verbose flags)
- DI-based resolve_pane_by_parent_chain helper with unit tests
- Docs in rust/tmux_helper/CLAUDE.md with exit-code contract
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 54 minutes and 11 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdded comprehensive documentation for a new Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Addresses review feedback on PR idvorkin#76 — the original 9 tests covered the core walker logic well but missed the sentinel guards and the /proc parser. Adds 6 tests for: - Walker starting from pid 0 and pid 1 (must return None without querying the reader; prior tests only matched pid 0/1 at the end of a chain, never at the start) - read_ppid_from_proc pid 0 guard (early return, no fs hit) - read_ppid_from_proc nonexistent pid (u32::MAX) — graceful None, not panic, exercises the .ok()? short-circuit - read_ppid_from_proc against real /proc/1/stat on Linux — init's ppid is 0, which proves the rfind(')')-based comm parser works on actual kernel data (the exact bug the parser exists to prevent) - read_ppid_from_proc(self) must return Some(non-zero) — end-to-end field-offset sanity check on a live stat line No implementation changes; tests only. All 187 tests pass, clippy clean on new code (4 pre-existing link_picker warnings unchanged). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR idvorkin#76 review feedback asked for dependency injection at the CLI command layer so surface tests for --json / --verbose / --pid / exit codes 2 and 3 become reachable. The previous round had to reject those tests because parent_pid_tree_cmd called tmux and /proc directly and wrote to stdout via println!, leaving no seam for a test to observe. Humble Object pattern: TmuxProvider + ProcReader traits cover the shelling layer; RealTmuxProvider and RealProcReader are the production impls. The testable core run_parent_pid_tree takes the traits plus a ParentPidTreeArgs struct and an explicit self_pid, and returns a ParentPidTreeOutcome (stdout string, stderr lines, exit code). The thin parent_pid_tree_cmd wrapper — the only code that constructs Real* impls and does real I/O — is what main() still calls. No behavior change; 187 existing tests pass. Addresses review feedback on PR idvorkin#76: add DI support to the CLI command layer so CLI surface tests become reachable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous test-coverage pass had to reject CLI surface tests for --json / --verbose / --pid / exit codes 2 and 3 because there was no seam to observe stdout/stderr or swap out tmux and /proc. With run_parent_pid_tree now accepting injected TmuxProvider + ProcReader and returning a structured ParentPidTreeOutcome, those tests become trivial. Adds 11 new tests driving the testable core through in-memory mocks: - default JSON output shape (pane_id, pane_pid, walked_from_pid, ancestors) - --pid explicit override walks from that pid - --pid explicit override does NOT consult self_pid's ppid (exit-3 bypass) - default plain output is just the pane id (shell-assignable contract) - --verbose emits walk chain to stderr with arrow format - exit 1: no match, empty stdout, human-readable stderr - exit 2: NotRunning variant, canonical stderr message - exit 2: ListFailed variant carries io::Error detail in stderr - exit 3: cannot read self ppid, empty stdout, path error on stderr - flag combo: --json + --pid emits JSON from explicit pid - flag combo: --json + --verbose keeps streams separated, no leakage Brings the file-level test count from 187 to 198. No behavior change. Addresses review feedback on PR idvorkin#76: add CLI surface tests previously rejected as unreachable. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The other tmux call sites in this binary (side_edit, side_run, rename_all,
rotate, third, info, etc.) currently shell out directly via run_tmux_command
and scattered Command::new("tmux") calls. None of them have characterization
tests, so bulk-migrating them to the new TmuxProvider trait would lose the
"tests pass" safety net. Igor asked for the trait "throughout" — doing that
safely requires tests first.
Leaves a TODO comment above run_tmux_command flagging the unmigrated sites,
and documents the humble-object pattern in tmux_helper/CLAUDE.md so future
additions follow it: put shell-outs behind TmuxProvider, keep logic in a
pure function that takes the trait object, make the command wrapper thin.
Addresses review feedback on PR idvorkin#76: scope management — migrate where
safe, flag the rest as follow-up work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tails Prints the full ancestor walk as a human-readable tree with cmdline / comm / exe per PID, plus the terminal pane_id. Uses the existing TmuxProvider / ProcReader traits (from 47b1ebb) so the tree-building logic and the formatter are both testable via DI. `--tree` composes cleanly with `--json` (structured chain output), `--verbose` (walk lines on stderr, tree on stdout), and `--pid` (override start pid). Default behavior unchanged when the flag is omitted. Example: $ rmux_helper parent-pid-tree --tree parent-pid-tree ├─ [pid 583169] bash /usr/bin/bash -c rmux_helper parent-pid-tree --tree │ exe: /usr/bin/bash ├─ [pid 4114505] claude claude /startup-larry ... --channels plugin:telegram@claude-plugins-official │ exe: /home/developer/.local/bin/claude └─ [pid 2594534] zsh /home/linuxbrew/.linuxbrew/bin/zsh (pane shell) exe: /home/linuxbrew/.linuxbrew/bin/zsh tmux pane: %35 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds a new install-completions subcommand that generates shell completions (zsh/bash/fish/powershell/elvish) and writes them to conventional paths. Supports --print-only (stdout) and --dry-run (report target path only). Powered by clap_complete with dynamic completion enabled — `parent-pid-tree --pid <TAB>` resolves live pids from /proc at tab-time, annotated with each process's comm. Humble-object refactor: detect_shell_from_env, completion_install_path, and enumerate_pid_candidates are pure functions driven by an EnvSnapshot struct + injected ProcReader, covered by 14 new unit tests. Recovered from an in-progress working tree left by a sibling subagent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Companion doc update for the install-completions subcommand. Covers default install paths, --print-only / --dry-run semantics, the tab-time dynamic completion hook, and the clap_complete unstable-dynamic feature flag dependency. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… tree
Previously, rich per-pid metadata (cmdline/comm/exe) only appeared in
JSON when --tree was set. --json --verbose emitted the minimal payload
plus stderr walk lines — useful but forced callers to pick between
human-readable tree OR structured rich JSON. Now either --tree or
--verbose triggers the full chain[] array in JSON, with --verbose
also keeping the stderr walk lines for human inspection. The minimal
--json output (without either detail flag) stays untouched for
scriptability.
Also adds role markers to distinguish walk positions:
- Tree text: "(start)" on the root line, "(pane shell)" on the
leaf (already existed), "(no pane found)" when exit 1, and
"(start, pane shell)" / "(start, no pane found)" for
single-entry chains.
- JSON: `role` field per entry with values {start, ancestor,
pane_shell, start_and_pane_shell, walked_past_root}.
Role derivation is extracted into a pure `chain_entry_role` helper so
tests cover the policy independently of the walker. Args struct gains
`wants_rich_chain()` predicate to keep the rich/minimal JSON switch
in one place.
Observed by Igor in interactive use: the old output made root and
leaf visually identical mid-chain, hiding which pid the walk
actually started from when passing --pid <N>.
Adds 12 new tests (234 total, was 222): rich vs minimal JSON paths,
verbose text-mode unchanged, tree-text start annotation, single-entry
combined annotation, no-pane-found leaf annotation, JSON role values
for each walk shape, and a pure unit test for chain_entry_role.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
parent-pid-treesubcommand tormux_helperthat walks the caller's parent-PID chain againsttmux list-panesto deterministically find the caller's owning tmux pane.rust/tmux_helper/CLAUDE.mdwith exit-code contract and usage.Motivation
Observed 2026-04-14:
harden-telegram'swatchdog.pyauto-recovery sent/reload-pluginsto the wrong Claude session for ~45 minutes because it resolved the target pane viatmux display-message -p '#{pane_id}', which returns the tmux-active pane, not the caller's pane. With two Claude sessions running concurrently (Larry in pane %35, blog in pane %65), the watchdog reloaded the wrong session every time.The correct primitive is a parent-PID walk: start from the caller's pid, read
/proc/<pid>/statfield 4 for ppid, and match ancestors againsttmux list-panes -a -F '#{pane_pid}'. First match is the answer. That's deterministic regardless of focus state.This PR encapsulates that primitive in
rmux_helperso future tmux-integration code can callrmux_helper parent-pid-treeand trust the answer instead of hand-rolling the walk. A companion PR inchop-conventions(#101) is fixing the specific watchdog.py case; this one makes the primitive reusable.Test plan
cargo testpasses with the new unit tests (181 total, 9 new)cargo clippy --all-targetsclean on new code (pre-existinglink_pickerwarnings unchanged)rmux_helper parent-pid-treefrom inside a tmux pane prints that pane's idrmux_helper parent-pid-tree --pid 1(init) returns no-match, exit 1rmux_helper parent-pid-tree --verboseshows the walk chain on stderrrmux_helper parent-pid-tree --jsonemits valid JSONLive smoke test output (from a tmux pane):
Follow-up:
--treeAdded a
--treeflag that prints the full ancestor chain as a visual tree with per-PID metadata: shortcomm, fullcmdline(from/proc/<pid>/cmdline, null-separated argv joined with spaces), andexereadlink. Leverages theTmuxProvider/ProcReaderDI layout from the earlier commits so the tree-builder and formatters are testable in isolation.Composition:
--treealone — ASCII tree on stdout, exit 0/1 matches the walk result--tree --json— structured payload (start_pid,pane_id,pane_pid,chain[]withpid/comm/cmdline/exe)--tree --verbose— tree on stdout, walk log on stderr (streams don't cross)--tree --pid N— tree for an arbitrary start pid--tree) — unchanged; still emits just%NKernel-thread / permission-denied paths degrade gracefully: empty cmdline falls back to
[comm]; unreadableexeprintsexe: (unreadable)(ornullin JSON). Long cmdlines truncate at 120 chars with…in the text view; full values always available via--json.Eleven new tests cover: metadata collection, missing cmdline/exe handling, tree+json combination, no-match tree population (for debugging), formatter truncation, ASCII box-drawing, pane annotation at the leaf, and a real-
/procsmoke test against the test process itself.Live sample:
🤖 Generated with Claude Code
Follow-up: shell completions
Adds an
install-completionssubcommand that writes shell completion scripts to the conventional location for zsh/bash/fish (powershell/elvish supported via--print-only). Auto-detects shell from$SHELL; override via--shell. Supports--dry-run(reports target path) and--print-only(stdout) for scripted use — mutually exclusive via clap.Default install paths:
$ZDOTDIR/.zfunc/_rmux_helperor$HOME/.zfunc/_rmux_helper$XDG_DATA_HOME/bash-completion/completions/rmux_helperor$HOME/.local/share/bash-completion/completions/rmux_helper$XDG_CONFIG_HOME/fish/completions/rmux_helper.fishor$HOME/.config/fish/completions/rmux_helper.fishDynamic completion — live values at tab-time
parent-pid-tree --pid <TAB>resolves live pids from/procat tab-time, annotated with each process'scommas the completion description — capped at 500 entries, sorted newest-first so the most recently spawned processes appear first.Uses
clap_complete::CompleteEnv::with_factory(Cli::command).complete()at the top ofmain()(featureunstable-dynamic). When invoked withCOMPLETE=<shell>in env, clap_complete intercepts and emits the shell script to stdout. The installed shell script re-invokes the binary withCOMPLETEset on every tab press, so values are always live — no static snapshot to regenerate on upgrade.Static completions (subcommand names, flag names, enum values like
--shell <TAB>) come free from clap.side-edit <file>is annotated withValueHint::FilePathfor default shell file completion.Live smoke
Tests
14 new unit tests (shell detection, install-path resolution per shell+XDG, pid enumeration / truncation, missing-
/proctolerance, clap-level conflicts and unknown-shell rejection).cargo testnow: 222 passed, 0 failed (was 208).Summary by CodeRabbit
New Features
parent-pid-treecommand with--json,--pid, and--verboseflags for resolving tmux pane ownership via parent process chain.Chores