π Sighting (dev box, 2026-08-28, the v5.4.0 canary rebuild)
./build.sh (and ./launch.sh) over the running dev stack fail preflight with:
preflight: β Port 8080 is already in use by an unidentified process (re-run ss -ltnp with sudo to name it) β this stack needs it free before launching.
β¦exit 3. The "unidentified process" is our own genwave-api-1. The only way through is the gh-#19 escape hatch (SKIP_PREFLIGHT=1 on both scripts), which throws away every other preflight check to get past this one.
π Root cause
tools/preflight.sh:237 β preflight_port_is_docker_owned greps the docker ps --format '{{.Ports}}' blob for [:]${port}->. Docker collapses adjacent published ports into a range, and the api publishes 8080 and 8081, so the blob reads:
0.0.0.0:8080-8081->8080-8081/tcp, [::]:8080-8081->8080-8081/tcp
:8080-> never occurs; :8080-8081-> does. 8081 fares worse β it is inside the range and never appears after a colon at all. The check therefore fails to recognise its own stack exactly when the stack is the shape compose.yaml ships.
π§ Fix
Expand ranges before matching (or match [:]${port}(-[0-9]+)?-> and test range membership for lo-hi). Pin it with the existing test seam β GW_SS_CMD/the docker-ports blob are already injectable β with a fixture blob carrying a collapsed range, one fact per shape: exact port, range start, port inside the range, port after the range (must still fail).
Also worth making the owner line honest: on a non-root shell ss -ltnp cannot name a root-owned docker-proxy, so "unidentified" is the expected result for any Docker-published port β the docker-owned check is the one that has to be right.
Refs: gh-#19 (preflight), gh-#530 (wizard β the same preflight runs there), F134.3a.
π Sighting (dev box, 2026-08-28, the v5.4.0 canary rebuild)
./build.sh(and./launch.sh) over the running dev stack fail preflight with:β¦exit 3. The "unidentified process" is our own
genwave-api-1. The only way through is the gh-#19 escape hatch (SKIP_PREFLIGHT=1on both scripts), which throws away every other preflight check to get past this one.π Root cause
tools/preflight.sh:237βpreflight_port_is_docker_ownedgreps thedocker ps --format '{{.Ports}}'blob for[:]${port}->. Docker collapses adjacent published ports into a range, and the api publishes 8080 and 8081, so the blob reads::8080->never occurs;:8080-8081->does. 8081 fares worse β it is inside the range and never appears after a colon at all. The check therefore fails to recognise its own stack exactly when the stack is the shapecompose.yamlships.π§ Fix
Expand ranges before matching (or match
[:]${port}(-[0-9]+)?->and test range membership forlo-hi). Pin it with the existing test seam βGW_SS_CMD/the docker-ports blob are already injectable β with a fixture blob carrying a collapsed range, one fact per shape: exact port, range start, port inside the range, port after the range (must still fail).Also worth making the owner line honest: on a non-root shell
ss -ltnpcannot name a root-owneddocker-proxy, so "unidentified" is the expected result for any Docker-published port β the docker-owned check is the one that has to be right.Refs: gh-#19 (preflight), gh-#530 (wizard β the same preflight runs there), F134.3a.