# 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:**
| 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`.
| 👈 | 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:**
| `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:**
| 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
| 5 | [Session Listing](../param_group/05_session_listing.md) | `--mode`, `--columns`, `--wide`, `--pid`, `--inspect` | All 5 params exclusive to `ps` |
### Referenced User Stories
| 26 | [026_session_listing.md](../user_story/026_session_listing.md) | Developer / CI operator |