claude_runner 1.4.1

CLI for executing Claude Code via builder pattern; YAML schema constants for command registration
Documentation
# CLI Command: ps

List all running Claude Code processes and queued `clr` waiters in two plain-style
tables showing per-session PID, elapsed time, CPU%, RAM, state, working directory,
and last-known task.

**Syntax:**

```sh
clr ps [OPTIONS]
```

**Parameters:**

| # | Parameter | Type | Default | Description |
|---|-----------|------|---------|-------------|
| 58 | [`--mode`]../param/058_mode.md | enum | `all` | Filter sessions by execution mode (`all`/`interactive`/`print`) |
| 59 | [`--columns`]../param/059_columns.md | string | 9 default cols | Comma-separated list of column keys to display |
| 60 | [`--wide`]../param/060_wide.md | bool | false | Show all 11 columns (convenience shorthand) |
| 68 | [`--pid`]../param/068_pid.md | string || Comma-separated PIDs; restrict active table to matching sessions only |
| 69 | [`--inspect`]../param/069_inspect.md | bool | false | Switch output to key:value record format showing all 12 attributes per session |

The flags `--help` and `-h`, and the positional token `help`, print subcommand
help and exit 0.

**Output Format:**

Two plain-style tables printed consecutively. Each table is preceded by a titled
caption rule line (e.g., `─── Active Sessions · 2 running ──────`) that names the
table and shows the item count. The column header row follows the caption.

1. **Active Sessions** — caption: `Active Sessions · N running`; default columns:
   `#`, `PID`, `Elapsed`, `CPU%`, `RAM`, `State`, `Mode`, `Absolute Path`, `Task`.
   Optional columns (hidden by default): `Command`, `Binary`. Use
   `--wide` to show all 11 columns, or `--columns` to select a custom subset.
2. **Queued CLR Processes** — caption: `Queued · N waiting`; columns: `#`, `PID`,
   `CWD`, `Waiting`, `Attempt`. Shows `clr` processes currently blocked in
   `wait_for_session_slot()`. Column selection (`--columns`/`--wide`) does not
   affect queued table columns.

Plain-style formatting: no outer borders; dash separator under header row; 2-space
column gaps. Header row uses plain text (no Unicode box-drawing characters).

**Row ordering:** Active session rows are ordered by session start time, oldest first
(lowest epoch value in `/proc/{pid}/stat` field 22). Queued rows are ordered by PID
ascending.

**`Elapsed` column format** (active sessions) and **`Waiting` column** (queued):
- `< 60 s``"45s"`
- `< 1 h``"8m 30s"`
- `≥ 1 h``"2h 15m"`

**Empty-state rules:**
- No active sessions and no queued processes → prints `No active Claude Code sessions.`
  and exits 0 (only this message, no tables).
- Active sessions present, no queued processes → prints active sessions table only.
- Queued processes present → prints active section (table or `No active Claude Code
  sessions.`), blank line, then the queued table.

- The current `clr ps` process is never listed as a row (self-exclusion).
- Data sources: `/proc/{pid}/stat` (state, CPU, start time), `/proc/{pid}/status` (RAM),
  `~/.claude/projects/` JSONL files (Task column), `/proc/{pid}/fd/0` symlink
  (interactive TTY fallback for Task), `$CLR_GATE_DIR` (default `/tmp/clr-gate/`) JSON
  files for queued processes.
- **Path shortening:** When the `PRO` environment variable is set and a session's working
  directory starts with that path, the `Absolute Path` column (active) and `CWD` column
  (queued) display the path with the `$PRO` prefix replaced by the literal string `"$PRO"`
  (e.g. `$PRO/myrepo`). Falls back to the full absolute path when `PRO` is unset, empty,
  or does not match.

**Gate state files:**

`gate.rs` writes a JSON state file to `$CLR_GATE_DIR/{pid}.json` when a `clr` process
enters `wait_for_session_slot()`. The file is updated each polling iteration and removed
automatically by a `GateFile` RAII Drop guard on normal return, panic, and Rust-level
stack unwind. The Drop guard does not protect against SIGKILL (which bypasses destructors)
— the liveness filter in `build_queued_table()` handles that case.

File format:

```json
{"cwd": "/path/to/cwd", "since": 1720000000, "attempt": 3, "message": "waiting for session slot"}
```

- `cwd` — working directory of the waiting `clr` process
- `since` — Unix timestamp when waiting began
- `attempt` — current poll iteration (0-based)
- `message` — human-readable status string

**PID liveness filtering and self-healing cleanup:**

`build_queued_table()` probes `/proc/{pid}` existence for every gate file before rendering
a row. Gate files whose PID no longer exists on the system are filtered out and deleted
(self-healing cleanup). This prevents orphaned gate files left by SIGKILL or crash from
appearing as perpetual phantom waiters in the queued table.

**Inspect output format (`--inspect`):**

When `--inspect` is active, each matching session produces a key:value record block
separated by a blank line. Table rendering is bypassed entirely. The Queued CLR
Processes table is suppressed. `--columns` and `--wide` are ignored. Example block:

```
──── PID 1234567 ─────────────────────────────────────────────
pid:      1234567
mode:     interactive
elapsed:  72h 18m
cpu:      0.6%
ram:      96M
state:    S
path:     $PRO/lib/wip_core/agent_kit/claude_runner/module/claude_runner/src
task:     explore current crate, its documents
binary:   /usr/local/bin/claude
cmd:      --effort max --dangerously-skip-permissions
cmdline:  /usr/local/bin/claude --effort max --dangerously-skip-permissions
started:  1750170000
```

Attributes: `pid`, `mode`, `elapsed`, `cpu`, `ram`, `state`, `path`, `task`,
`binary`, `cmd`, `cmdline`, `started` (12 total). `started` is Unix epoch seconds.
`--pid` and `--mode` filters apply normally when combined with `--inspect`.

**Session Flags:**

Each active session row may carry zero or more flag emoji in a conditional `Flags` column.
The `Flags` column appears between `State` and `Mode` and is rendered in a **two-pass** approach:

1. **First pass:** compute flags for every session row.
2. **Second pass:** if all rows have empty flags, omit the `Flags` column entirely from the rendered table; otherwise include it.

When ≥1 flag is present in any row, a **legend** is printed below the active sessions table as a
single line listing only the symbols that appear in the current output (not all 7 symbols).
Format: `<emoji> <Name>` entries separated by two spaces, in canonical order 👈🖨⚡🕰🐘⚠🐳.
Example: `🖨 Print mode  🐘 High RAM  ⚠ Dead metrics  🐳 Container`.

| Flag | Name | Condition | Default threshold |
|------|------|-----------|-------------------|
| 👈 | This session | `getppid()` of the `clr ps` process resolves to a `claude` process ||
| 🖨 | Print mode | session cmdline contains `--print` or `-p` ||
|| Active | CPU delta ≥ 3 ticks in 1-second sample window (`/proc/{pid}/stat` fields 14+15 read twice, 1 s apart) ||
| 🕰 | Ancient | `elapsed_secs > CLR_PS_ANCIENT_SECS` | 28800 (8 h) |
| 🐘 | High RAM | `ram_mb > CLR_PS_HIGH_RAM_MB` | 400 MB |
|| Dead metrics | `started_at` is `None` (all metric fields show `-`) ||
| 🐳 | Container | `cwd` does not start with `$HOME` ||

**Threshold env vars:**

| Variable | Type | Default | Notes |
|----------|------|---------|-------|
| `CLR_PS_ANCIENT_SECS` | u64 | 28800 | Seconds elapsed before 🕰 fires; invalid values silently ignored |
| `CLR_PS_HIGH_RAM_MB` | u64 | 400 | RSS megabytes before 🐘 fires; invalid values silently ignored |

**Implementation note:** `getppid()` is obtained via `std::os::unix::process::parent_id()`.
The 👈 flag fires only when the parent process basename (read from `/proc/{ppid}/cmdline`) equals `claude`.
If `clr ps` is invoked from a shell, the parent is `bash` (or similar) and the flag is absent.

**Exit Codes:**

| Code | Meaning |
|------|---------|
| 0 | Success — table(s) or inspect blocks printed, or no-sessions message printed |
| 1 | Error (invalid `--mode` value, unknown `--columns` key, non-numeric `--pid` value, or unexpected I/O failure) |

**Mode detection:**

Each active session is classified as `interactive` or `print` by examining its
NUL-delimited `/proc/{pid}/cmdline` arguments. If any argument equals `--print`
or `-p`, the session is classified as `print`; otherwise `interactive`. The
`--mode` filter uses this classification to include or exclude rows.

**Examples:**

```sh
# List all running sessions and queued waiters
clr ps

# Show only print-mode (headless) sessions
clr ps --mode print

# Show all 11 columns including command and binary (mode is a default column)
clr ps --wide

# Custom column selection in specified order
clr ps --columns pid,path,mode,task

# Wide output filtered to interactive sessions only
clr ps -w --mode interactive

# Filter to specific PIDs
clr ps --pid 1234567
clr ps --pid 1234567,2345678

# Inspect full attributes of all sessions
clr ps --inspect

# Inspect specific session
clr ps --pid 1234567 --inspect

# Show ps-specific help
clr ps --help

# Env-var equivalents
CLR_PS_MODE=print clr ps
CLR_PS_COLUMNS=pid,elapsed,cmd clr ps
CLR_PS_PID=1234567 clr ps

# Discover known subcommands (ps appears in help)
clr help
```

**Notes:**

`ps` is Linux-only; per-process metrics come from `/proc` and are not available
on macOS or Windows. On non-Linux platforms the command is not compiled in.

The Task column shows the last Form A (plain-string content) user message from
the session's JSONL log, truncated to 35 chars. Form A messages have structure
`{"type":"user","message":{"role":"user","content":"<text>"}}`. Form B messages
(`"content":[...]` array — tool_result / skill invocations) are skipped. Falls
back to `interactive` when no JSONL is found or no Form A entry is present.

`clr p` and `clr pss` trigger the "Did you mean 'ps'?" typo guard and exit 1.

The `CLR_GATE_DIR` environment variable overrides the default `/tmp/clr-gate/` gate
state directory — used in tests to isolate gate file I/O from real system state.

### Referenced Parameter Groups

| # | Group | Params | Notes |
|---|-------|--------|-------|
| 5 | [Session Listing]../param_group/05_session_listing.md | `--mode`, `--columns`, `--wide`, `--pid`, `--inspect` | All 5 params exclusive to `ps` |

### Referenced User Stories

| # | User Story | Persona |
|---|------------|---------|
| 26 | [026_session_listing.md]../user_story/026_session_listing.md | Developer / CI operator |