# Seed: $OWL list silently deletes live psyche perches

**Source:** https://paste.rs/OFRoZ (captured 2026-05-27)
**Severity:** HIGH — data loss in read-only command
**Surface:** v1.11.20 (current live)

## The Problem

`$OWL list` — intended as a read-only operation — was silently deleting live psyche perch directories. When a wrapper polled during `list` execution, the command would classify the perch as "orphaned" (based on temporarily missing files during normal operation) and perform a recursive deletion via `fs::remove_dir_all()`.

**Root cause:** The orphan-cleanup logic in `list_filter::collect()` violated two invariants simultaneously:

1. **CQRS violation:** A read command mutated persistent state
2. **Lifecycle violation:** Psyche perches should only be soft-cleaned (removing sentinels while preserving data), never hard-deleted

## The Fix (Three Coordinated Changes)

### Fix 1: Decouple Cleanup from List (CQRS)
- Disable `cleanup_orphans` at all `list` call sites (set to `false` explicitly)
- Move orphan GC to `live doctor --fix` as the sole authorized cleanup surface
- Track orphan counts in output and display a hint: `"(N orphan directories present — run `live doctor --fix` to GC)"`
- Add tests confirming list never deletes and properly counts orphans

### Fix 2: Harden Orphan Detection (Defense in Depth)
Three guards prevent false-positive classifications:

- **2a. Wrapper-PID liveness:** Check if owning wrapper process is alive; if so, directory is not orphaned
- **2b. Grace period:** Require 60-second age before orphan classification (avoids mid-rewrite races)
- **2c. Atomic writes:** Route all `info.json` writes through `atomic_write_string()` to eliminate parse failures during concurrent reads

### Fix 3: Never Hard-Delete Nested Perches
- Route cleanup decisions: nested perches → soft-clean only; empty top-level dirs → hard delete
- Soft-clean preserves data for recovery; hard-delete is only for truly empty orphans
- Update status labels to distinguish `SOFT-CLEANED` from `CLEANED`

## Deployment Notes

- Single coordinated rollout via standard GSD workflow (`/gsd-plan-phase`)
- Atomic commits per fix subsection for independent revertability
- Test suite validation before/after each change
- Verification includes wrapper stability under concurrent list operations and proper classification of test orphans

## Related (NOT this bug — separate threads)
- `.planning/debug/echo-fire-orphan-grace-violation-probetest.md` — wrapper orphan ECHO-FIRE before grace recheck (fixed)
- `.planning/debug/file-drop-file-gone-todlando.md` — `psyche-download` destructive consume vs spool envelope (diagnosed)

This bug = `list_filter::collect()` `fs::remove_dir_all` invoked from read command. Distinct surface.
