--- name: e2e-port-squat-triage description: "When an e2e seam on a fixed port fails weirdly (404s on routes that exist, stale behavior, health probe passes but app seems ancient), audit for leftover dev/debug servers squatting the port before believing any red or green." --- # E2E fixed-port squat triage ## Symptom classes (all seen with a stale server squatting the seam's fixed port) - Routes that exist in current `dist`/source return 404 ("Cannot POST /api/...") while unit tests through the same app factory pass. - A seam that is *green* but exercises old code: its setup curls `http://host:PORT/health` and gets a response from **whoever owns the port** — a leftover server, not the one it just spawned (the spawned one dies or idles with EADDRINUSE, sometimes still showing in `pgrep`). - Behavior matches an older build (missing mounts/guards) despite a fresh build step. ## Triage recipe (before touching code) 1. Who owns the port: ```bash ss -tlnp 2>/dev/null | grep :PORT ``` 2. List every server process with **start times**: ```bash for p in $(pgrep -f "path/to/server-entry"); do ps -o pid,lstart,cmd -p $p --no-headers; done ``` 3. Compare start times against the build artifact timestamps (`ls -la dist/...`). A server older than the dist you just built is a liar — its in-memory modules predate the code under test. 4. Kill all leftovers, then re-check: `pgrep -f "server-entry" || echo all-dead`. 5. Rerun the seam. Only now is red/red and green/green meaningful. ## Why it happens - `kill %1` in **non-interactive** bash fails silently (no job control) — use explicit PIDs or `pkill -f `. - Seam setups that "wait for health" cannot distinguish their own server from a squatter on the same port; a pre-flight "port must be free" assertion is the permanent fix (assert *before* spawn). ## Hardening (if you own the seam) Add to setup, before spawning: fail fast if the port is already listening — turns the silent liar into a loud error: ```bash ss -tln "sport = :PORT" | grep -q LISTEN && { echo "port PORT busy — kill leftovers first" >&2; return 1; } ```