# `Gralkor.OrphanReaper`
[🔗](https://github.com/elimydlarz/gralkor/blob/main/lib/gralkor/orphan_reaper.ex#L1)

Pre-OTP cleanup for a stale uvicorn left bound to Gralkor's port.

Intended to run *before* the OTP supervision tree starts — specifically
before `Gralkor.Server`'s boot sequence, whose `:port_in_use` check
refuses to clean up foreign holders and crashes.

Rationale: a BEAM abort (Ctrl+C → `a`, SIGKILL, crash) doesn't reliably
run Gralkor.Server's graceful-shutdown path, which is the only path
that SIGTERMs the uvicorn OS pid. So aborts sometimes leave uvicorn orphaned
(reparented to launchd) with port 4000 still bound. The reaper looks
for such an orphan, verifies it is ours (command line contains every
one of `@identifiers` — the invariant shape of the uvicorn invocation
that `Gralkor.Server` spawns, regardless of mix layout or symlinked
priv paths), and SIGKILLs it. If the holder is anything else, the
reaper raises — we don't kill foreign processes.

Path-based identification was tried first and dropped: under path
deps, mix symlinks the priv dir, and `ps` reports the resolved
physical path while `:code.priv_dir(:gralkor_ex)` returns the symlink
— substring match fails on the same directory. Command-line
identifiers are layout-independent.

`System.cmd/3` is injectable via `opts[:shell]` so the logic is
unit-testable without side effects.

# `shell`

```elixir
@type shell() :: (String.t(), [String.t()], keyword() -&gt; {String.t(), integer()})
```

# `reap`

```elixir
@spec reap(keyword()) :: :ok | no_return()
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
