# Test: `ps`
Integration test planning for the `ps` command. See [command/06_ps.md](../../../../docs/cli/command/06_ps.md) for specification.
`ps` lists running Claude Code processes and queued `clr` waiters in two plain-style
tables. The ≥1-session tests spawn a fake `claude` background process using the
`fake_claude_binary_dir` helper and prepend it to PATH. Gate file tests create a
temp dir as `CLR_GATE_DIR` and write a fake JSON state file to exercise the
queued-sessions table path without a live `gate.rs` waiter. 0-session tests pass
an empty temp dir as `CLR_PROC_DIR` so `find_claude_processes()` sees no entries
regardless of live Claude sessions on the host (see `claude_core::process`).
## Test Case Index
| IT-1 | `clr ps` with 0 sessions → exit 0, no-sessions message | No-sessions |
| IT-2 | `clr ps` with ≥1 session → exit 0, plain table (no `┌` border) | Sessions present |
| IT-3 | `clr --help` lists `ps` | Help listing |
| IT-4 | `clr p` (typo) → exit 1, stderr: Did you mean | Typo guard |
| IT-5 | `clr ps` table contains `PID`, `Elapsed`, `Absolute Path`, `Task` headers | Column presence |
| IT-6 | `clr pss` (typo) → exit 1, stderr: Did you mean | Typo guard |
| IT-7 | `clr ps` PID not in own output | Self-exclusion |
| IT-8 | `clr ps --unknown` → exit 1 | Error handling |
| IT-9 | `$PRO` prefix replaced by `"$PRO"` in Absolute Path column | Path shortening |
| IT-10 | Gate file present → queued table with `PID`, `CWD`, `Waiting` headers | Queued present |
| IT-11 | No gate files → no queued table in output | Queued absent |
| IT-12 | Active table caption contains `Active Sessions` and `running` | Caption presence |
| IT-13 | Orphaned gate file (dead PID) filtered out of queued table | BUG-293 repro |
| IT-14 | `clr ps --help` → exit 0, stdout contains help text | Help flag |
| IT-15 | `clr ps -h` → exit 0, stdout contains help text | Help flag |
| IT-16 | Task column extracts Form A JSONL content for known session | BUG-295/296/297 repro |
| IT-17 | Task column selects last Form A entry, skips Form B tool_result lines | BUG-297 repro |
| IT-18 | `clr ps help` (positional) → exit 0, stdout contains help text | Help flag |
| IT-19 | Task column extracts Form A content for CWD with no underscores (regression) | BUG-295 regression |
| IT-20 | Active sessions ordered by start time (oldest first) | Sort order |
| IT-21 | `clr ps --mode print` shows only print-mode sessions | Mode filter |
| IT-22 | `clr ps --mode interactive` shows only interactive sessions | Mode filter |
| IT-23 | `clr ps --mode bogus` exits 1 with error | Mode validation |
| IT-24 | `clr ps --columns pid,path,task` shows custom columns in order | Column select |
| IT-25 | `clr ps --columns bogus` exits 1 with error listing valid keys | Column validation |
| IT-26 | `clr ps --wide` shows all 11 columns including Mode, Command, Binary | Wide output |
| IT-27 | `clr ps --wide --columns pid,task` → `--columns` wins | Precedence |
| IT-28 | `CLR_PS_MODE=print` env var fallback filters print sessions | Env var |
| IT-29 | `CLR_PS_COLUMNS=pid,elapsed` env var fallback selects columns | Env var |
| IT-30 | `Flags` column absent when no session has any flag | Flags — column absent |
| IT-31 | 🐳 flag for session cwd outside `$HOME` — `Flags` column present | Flags — container |
| IT-32 | 🕰 flag when elapsed exceeds `CLR_PS_ANCIENT_SECS` threshold | Flags — ancient |
| IT-33 | 🐘 flag when RAM exceeds `CLR_PS_HIGH_RAM_MB` threshold | Flags — high RAM |
| IT-34 | ⚠ flag for TOCTOU-dead session (started_at is None) | Flags — dead metrics |
| IT-35 | 🖨 flag for print-mode session | Flags — print mode |
| IT-36 | Legend printed below active table when ≥1 flag present | Flags — legend |
| IT-37 | Legend absent when no flags present | Flags — legend absent |
| IT-38 | `CLR_PS_ANCIENT_SECS` / `CLR_PS_HIGH_RAM_MB` override thresholds | Flags — env vars |
| IT-39 | Sleeping process → no ⚡ flag (CPU delta = 0) | Flags — active absent |
| IT-40 | Busy-loop process → ⚡ flag present (CPU delta ≫ 3) | Flags — active present |
## Test Coverage Summary
- No-sessions: 1 test (IT-1)
- Sessions present: 1 test (IT-2)
- Help listing: 1 test (IT-3)
- Typo guard: 2 tests (IT-4, IT-6)
- Column presence: 1 test (IT-5)
- Self-exclusion: 1 test (IT-7)
- Error handling: 1 test (IT-8)
- Path shortening: 1 test (IT-9)
- Queued present: 1 test (IT-10)
- Queued absent: 1 test (IT-11)
- Caption presence: 1 test (IT-12)
- BUG-293 repro: 1 test (IT-13)
- Help flag: 3 tests (IT-14, IT-15, IT-18)
- BUG-295/296/297 repro: 1 test (IT-16)
- BUG-297 repro: 1 test (IT-17)
- BUG-295 regression: 1 test (IT-19)
- Sort order: 1 test (IT-20)
- Mode filter: 2 tests (IT-21, IT-22)
- Mode validation: 1 test (IT-23)
- Column select: 1 test (IT-24)
- Column validation: 1 test (IT-25)
- Wide output: 1 test (IT-26)
- Precedence: 1 test (IT-27)
- Env var: 2 tests (IT-28, IT-29)
- Flags — column absent: 1 test (IT-30)
- Flags — container: 1 test (IT-31)
- Flags — ancient: 1 test (IT-32)
- Flags — high RAM: 1 test (IT-33)
- Flags — dead metrics: 1 test (IT-34)
- Flags — print mode: 1 test (IT-35)
- Flags — legend: 1 test (IT-36)
- Flags — legend absent: 1 test (IT-37)
- Flags — env vars: 1 test (IT-38)
- Flags — active absent: 1 test (IT-39)
- Flags — active present: 1 test (IT-40)
**Total:** 40 tests
---
### IT-1: 0 sessions → no-sessions message
- **Setup:** empty temp dir passed as `CLR_PROC_DIR` so `find_claude_processes()` returns no results regardless of host sessions
- **Command:** `clr ps` (with `CLR_PROC_DIR=<empty_dir>` in env)
- **Expected behavior:** stdout = `No active Claude Code sessions.`; exit 0
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-2: ≥1 sessions → plain-style table
- **Setup:** fake `claude` binary placed in temp dir; PATH prepended; binary sleeps 30s to stay alive during test
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; stdout contains `PID` header; stdout does NOT contain `┌` (plain style, no borders)
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-3: `clr --help` lists `ps`
- **Command:** `clr --help`
- **Expected behavior:** stdout contains `ps`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-4: Typo `clr p` triggers guard
- **Command:** `clr p`
- **Expected behavior:** stderr contains `Did you mean`; exit 1
- **Exit:** 1
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-5: Table headers present
- **Setup:** ≥1 fake `claude` process running
- **Command:** `clr ps`
- **Expected behavior:** stdout contains `PID`, `Elapsed`, `Absolute Path`, and `Task`; exit 0
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-6: Typo `clr pss` triggers guard
- **Command:** `clr pss`
- **Expected behavior:** stderr contains `Did you mean`; exit 1
- **Exit:** 1
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-7: `clr ps` not listed in own output
- **Setup:** ≥1 fake `claude` process running; capture PID of `clr ps`
- **Command:** `clr ps`
- **Expected behavior:** The `clr ps` process PID is not present in stdout; exit 0
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** `find_claude_processes()` excludes the caller's PID; this test verifies end-to-end exclusion
---
### IT-8: Unknown flag → exit 1
- **Command:** `clr ps --unknown`
- **Expected behavior:** stderr contains an error message; exit 1
- **Exit:** 1
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-9: `$PRO` prefix shortened in Absolute Path column
- **Setup:** temp dir created as fake `$PRO`; subdirectory `my/project` created within it; fake `claude` ELF spawned with `current_dir = $PRO/my/project`; `PRO` set to temp dir path when running `clr ps`
- **Command:** `clr ps` (with `PRO=<temp_dir>` in env)
- **Expected behavior:** stdout contains `"$PRO"` (literal); stdout does NOT contain the full temp dir path; exit 0
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-10: Gate file present → queued table with headers
- **Setup:** temp dir used as `CLR_GATE_DIR`; write `{test_process_pid}.json` with valid gate JSON (`cwd`, `since`, `attempt`, `message`). Uses the test process's own PID so the `/proc/{pid}` liveness filter passes (gate files with dead PIDs are filtered out per BUG-293)
- **Command:** `clr ps` (with `CLR_GATE_DIR=<temp_dir>` in env)
- **Expected behavior:** Exit 0; stdout contains `PID`, `CWD`, and `Waiting` column headers (queued table)
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** No live `clr` waiter needed — the test writes the state file directly to exercise the read path. The PID must be alive for the liveness filter
---
### IT-11: No gate files → no queued table
- **Setup:** temp dir used as `CLR_GATE_DIR`; directory is empty (no JSON files); separate empty temp dir passed as `CLR_PROC_DIR` for process isolation
- **Command:** `clr ps` (with `CLR_GATE_DIR=<empty_gate_dir>` and `CLR_PROC_DIR=<empty_proc_dir>` in env)
- **Expected behavior:** Exit 0; stdout = `No active Claude Code sessions.`; stdout does NOT contain `Waiting` or `Attempt` headers
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-12: Active table caption contains `Active Sessions` and `running`
- **Setup:** fake `claude` binary placed in temp dir; PATH prepended; binary sleeps 30s
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; stdout contains `Active Sessions` (caption title) and `running` (count suffix)
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** The caption rule line (e.g., `─── Active Sessions · 1 running ──────`) appears above the column header row in the rendered table
---
### IT-13: Orphaned gate file filtered out (BUG-293)
- **Setup:** temp dir used as `CLR_GATE_DIR`; write `99999999.json` with valid gate JSON (PID 99999999 guaranteed dead — far above `pid_max`)
- **Command:** `clr ps` (with `CLR_GATE_DIR=<temp_dir>` in env)
- **Expected behavior:** Exit 0; stdout does NOT contain `Queued` (no queued table rendered); stdout does NOT contain `99999999`; the orphan `.json` file is deleted by self-healing cleanup
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** test_kind: bug_reproducer(BUG-293). Validates both liveness filtering AND self-healing file deletion
---
### IT-14: `clr ps --help` prints help
- **Command:** `clr ps --help`
- **Expected behavior:** Exit 0; stdout contains help text for the `ps` subcommand (e.g. column descriptions or `Active Sessions`)
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** test_kind: bug_reproducer(BUG-294). Mirrors BUG-222 regression pattern for kill/isolated/ask
---
### IT-15: `clr ps -h` prints help
- **Command:** `clr ps -h`
- **Expected behavior:** Exit 0; stdout contains help text for the `ps` subcommand
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** test_kind: bug_reproducer(BUG-294)
---
### IT-16: Task column extracts Form A JSONL content
- **Setup:** (1) fake `claude` binary placed in temp dir; PATH prepended; binary runs in a known CWD containing underscores (e.g. `$temp/wip_core/proj`). (2) Create synthetic `~/.claude/projects/` under a temp HOME dir: compute encoded path for the fake CWD by replacing both `/` and `_` with `-`; create that directory; write a JSONL file with one Form A user entry: `{"type":"user","message":{"role":"user","content":"fix the auth module"}}`. (3) Run `clr ps` with `HOME=<temp_home>`.
- **Command:** `clr ps` (with `HOME=<temp_home>` in env)
- **Expected behavior:** Exit 0; stdout Task column contains `fix the auth module` (truncated to 35 chars)
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** test_kind: bug_reproducer(BUG-295, BUG-296, BUG-297). Validates path encoding fix (BUG-295), content field fix (BUG-296), and Form A selection (BUG-297) end-to-end
---
### IT-17: Task column selects last Form A over Form B
- **Setup:** Same as IT-16 but the JSONL contains both a Form A entry and one or more Form B tool_result entries appearing after it: Form B line: `{"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"tu_abc","content":[{"type":"text","text":"claude command::some_skill"}]}]}}`. The Form A entry contains `"content":"the actual task"`. Form A appears before Form B in the file (simulating normal session order).
- **Command:** `clr ps` (with `HOME=<temp_home>` in env)
- **Expected behavior:** Exit 0; stdout Task column contains `the actual task`; stdout does NOT contain `claude command::some_skill`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** test_kind: bug_reproducer(BUG-297). Validates that Form B tool_result lines are skipped in favour of the last Form A human-typed entry. Use `std::process::Command` directly with `.env("HOME", &temp_home)` — `run_clr_ps()` does not accept HOME parameter.
---
### IT-18: `clr ps help` (positional) prints help
- **Command:** `clr ps help`
- **Expected behavior:** Exit 0; stdout contains help text for the `ps` subcommand
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** test_kind: bug_reproducer(BUG-294). Positional `help` token (no dashes). Mirrors the `dispatch_kill` / `dispatch_ask` pattern.
---
### IT-19: Task column extracts Form A for underscore-free CWD (regression)
- **Setup:** Same as IT-16 but CWD contains no underscores (e.g. `$temp/work/proj`). Encode path with only `/` → `-` (no underscore replacement needed). Write synthetic Form A JSONL under the correctly-encoded path in temp HOME.
- **Command:** `clr ps` (with `HOME=<temp_home>` in env)
- **Expected behavior:** Exit 0; stdout Task column shows Form A content value
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** Regression test — verifies BUG-295 fix (`replace('_', "-")`) does not break paths that originally encoded correctly with only slash replacement. Use `std::process::Command` directly with `.env("HOME", &temp_home)`.
---
### IT-20: Active sessions ordered by start time (oldest first)
- **Setup:** Spawn two fake `claude` binaries sequentially: process A first (older, lower PID), then process B (newer, higher PID). Both use `fake_claude_binary_dir()` helper + `.arg("30")` to stay alive. A brief `sleep(1)` between spawns ensures different `started_at` values in `/proc/{pid}/stat` field 22.
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; row `#1` has a longer `Elapsed` value than row `#2`. Parse the two `Elapsed` column values and assert that the first row's elapsed duration is ≥ the second row's.
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** test_kind: bug_reproducer(BUG-301). In typical Linux PID allocation, older processes have lower PIDs, so this test validates ordering is applied but cannot distinguish age-sort from PID-sort without `/proc` mocking. The behavioral guarantee is: oldest session appears first.
---
### IT-21: `--mode print` shows only print-mode sessions
- **Setup:** Spawn 2 fake `claude` processes: one with `--print` arg, one without. Both use `fake_claude_binary_dir()` + `.arg("30")`
- **Command:** `clr ps --mode print`
- **Expected behavior:** Exit 0; output contains the print-mode process PID; output does NOT contain the interactive process PID
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** Mode detection reads NUL-delimited `/proc/{pid}/cmdline` — `--print` or `-p` → print, else interactive
---
### IT-22: `--mode interactive` shows only interactive sessions
- **Setup:** Same as IT-21
- **Command:** `clr ps --mode interactive`
- **Expected behavior:** Exit 0; output contains the interactive process PID; output does NOT contain the print-mode process PID
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-23: `--mode bogus` → exit 1
- **Command:** `clr ps --mode bogus`
- **Expected behavior:** Exit 1; stderr contains error message listing valid values (`all`, `interactive`, `print`)
- **Exit:** 1
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-24: `--columns pid,path,task` shows custom columns
- **Setup:** ≥1 fake `claude` process running
- **Command:** `clr ps --columns pid,path,task`
- **Expected behavior:** Exit 0; stdout contains `PID`, `Absolute Path`, `Task`; does NOT contain `CPU%`, `RAM`, `State`, `Elapsed`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-25: `--columns bogus` → exit 1
- **Command:** `clr ps --columns bogus`
- **Expected behavior:** Exit 1; stderr contains error listing valid column keys
- **Exit:** 1
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-26: `--wide` shows all 11 columns
- **Setup:** ≥1 fake `claude` process running
- **Command:** `clr ps --wide`
- **Expected behavior:** Exit 0; stdout contains `Command` and `Binary` headers (plus the 9 default headers including `Mode`)
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-27: `--columns` overrides `--wide`
- **Setup:** ≥1 fake `claude` process running
- **Command:** `clr ps --wide --columns pid,task`
- **Expected behavior:** Exit 0; stdout contains `PID` and `Task`; does NOT contain `Mode`, `Command`, `Binary`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-28: `CLR_PS_MODE=print` env var fallback
- **Setup:** Spawn 2 fake `claude` processes: one print-mode, one interactive
- **Command:** `clr ps` with `CLR_PS_MODE=print` in env
- **Expected behavior:** Exit 0; output contains only print-mode session PID
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-29: `CLR_PS_COLUMNS=pid,elapsed` env var fallback
- **Setup:** ≥1 fake `claude` process running
- **Command:** `clr ps` with `CLR_PS_COLUMNS=pid,elapsed` in env
- **Expected behavior:** Exit 0; stdout contains `PID` and `Elapsed`; does NOT contain `CPU%`, `RAM`, `Task`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-30: `Flags` column absent when no session has any flag
- **Setup:** Fake `claude` ELF spawned in a subdirectory of `$HOME` (inside HOME, not ancient, not high-RAM, state not R, interactive mode, metrics readable); `CLR_PS_ANCIENT_SECS=999999` (prevent ancient false positive); `CLR_PS_HIGH_RAM_MB=999999` (prevent high-RAM false positive)
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; stdout does NOT contain `Flags` header; all rows have no flag symbols
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-31: 🐳 flag for session cwd outside `$HOME`
- **Setup:** Fake `claude` ELF spawned with `current_dir` set to `/tmp/container_work` (outside `$HOME`); PATH prepended; session stays alive 30s
- **Command:** `clr ps` with `HOME=<temp_home>` (ensure /tmp does not start with temp_home)
- **Expected behavior:** Exit 0; stdout contains `Flags` header; stdout contains `🐳`; legend below active table lists `🐳`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-32: 🕰 flag when elapsed exceeds `CLR_PS_ANCIENT_SECS` threshold
- **Setup:** Fake `claude` ELF running (any cwd is fine)
- **Command:** `clr ps` with `CLR_PS_ANCIENT_SECS=0` (every session is "ancient" — 0 threshold fires immediately)
- **Expected behavior:** Exit 0; stdout contains `🕰`; legend lists `🕰`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** Setting threshold to 0 ensures elapsed_secs > 0 triggers the flag without needing to wait 8 h in tests
---
### IT-33: 🐘 flag when RAM exceeds `CLR_PS_HIGH_RAM_MB` threshold
- **Setup:** Fake `claude` ELF running; `CLR_PS_HIGH_RAM_MB=0` (zero threshold — any non-zero RSS triggers the flag)
- **Command:** `clr ps` with `CLR_PS_HIGH_RAM_MB=0` in env
- **Expected behavior:** Exit 0; stdout contains `🐘`; legend lists `🐘`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** Setting threshold to 0 ensures any running process's RSS (always > 0 MB) triggers the flag
---
### IT-34: ⚠ flag for TOCTOU-dead session (started_at is None)
- **Setup:** Spawn a fake `claude` process that exits immediately after being found by `find_claude_processes()`; craft a test using `CLR_PROC_DIR` to inject a fake proc entry pointing to a dead PID — OR use a temp `/proc`-like dir with an entry missing `stat` file
- **Command:** `clr ps` (instrumented so find returns a PID with no readable `/proc/{pid}/stat`)
- **Expected behavior:** Exit 0; stdout contains `⚠`; legend lists `⚠`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** This is the same TOCTOU scenario documented in the `06_ps.md` Notes section. Implementation may use `CLR_PROC_DIR` to inject a proc dir with cmdline present but stat absent
---
### IT-35: 🖨 flag for print-mode session
- **Setup:** Spawn fake `claude` ELF with `--print` arg; `CLR_PS_ANCIENT_SECS=999999`; `CLR_PS_HIGH_RAM_MB=999999`; cwd inside $HOME
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; the print-mode session row contains `🖨`; legend lists `🖨`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-36: Legend printed below active table when ≥1 flag present
- **Setup:** Fake `claude` ELF spawned in `/tmp/no_home_dir` (triggers 🐳); `HOME=<temp_home>` so `/tmp` is outside HOME
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; stdout contains `Active Sessions` (active table); stdout contains a single legend line AFTER the active table; legend line contains `🐳` and `Container`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-37: Legend absent when no flags present
- **Setup:** Same as IT-30 (no flags firing); `CLR_PS_ANCIENT_SECS=999999`; `CLR_PS_HIGH_RAM_MB=999999`
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; stdout does NOT contain a legend line (no `👈`, `🖨`, `⚡`, `🕰`, `🐘`, `⚠`, `🐳` symbols in output)
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
---
### IT-38: `CLR_PS_ANCIENT_SECS` / `CLR_PS_HIGH_RAM_MB` override thresholds
- **Setup:** Fake `claude` ELF running; set `CLR_PS_ANCIENT_SECS=999999` and `CLR_PS_HIGH_RAM_MB=999999` (impossibly high — no session can trigger these flags)
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; stdout does NOT contain `🕰` or `🐘`; if no other flags fire, `Flags` column absent
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** Separate sub-cases: (a) `CLR_PS_ANCIENT_SECS=0` → 🕰 fires (IT-32); (b) `CLR_PS_HIGH_RAM_MB=0` → 🐘 fires (IT-33); (c) both set to 999999 → neither fires (this test)
---
### IT-39: Sleeping process → no ⚡ flag (CPU delta = 0)
- **Setup:** Fake `claude` process spawned via `sleep 300` (zero CPU consumption); `CLR_PS_ANCIENT_SECS=999999`; `CLR_PS_HIGH_RAM_MB=999999`; cwd inside `$HOME`
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; stdout does NOT contain `⚡`; `Flags` column absent (no other flags fire)
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** Validates the negative path: sleeping process accumulates 0 CPU ticks in the 1-second sample window → delta = 0 < 3 → no ⚡. **Host caveat:** on the host with any CPU-active live sessions, their ⚡ makes `!stdout.contains("⚡")` fail falsely; this test is reliable only in container (0 live sessions).
---
### IT-40: Busy-loop process → ⚡ flag present (CPU delta ≫ 3)
- **Setup:** Fake `claude` process spawned via `/bin/sh -c 'while :; do :; done'` (100% CPU); `CLR_PS_ANCIENT_SECS=999999`; `CLR_PS_HIGH_RAM_MB=999999`
- **Command:** `clr ps`
- **Expected behavior:** Exit 0; stdout contains `⚡`; legend contains `Active`
- **Exit:** 0
- **Source:** [command/06_ps.md](../../../../docs/cli/command/06_ps.md)
- **Note:** Busy-loop consumes ~100 ticks/s → delta ≈ 100 in 1 s window, well above threshold of 3. Also validates the renamed legend text ("Active" not "On CPU")