Task: Implement Hotfix: NATS cert generation fails on macOS/Windows (missing `ip` command)
Table of Contents
This page documents a task in the Hotfix: NATS cert generation fails on macOS/Windows (missing `ip` command) story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Make `_local_ipv4_addresses` in `projects/ores.compass/src/nats_certs.py` reach its existing gethostname fallback when the `ip` binary is absent, so NATS certificate generation (and therefore `compass env configure`) works on macOS and Windows.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Hotfix: NATS cert generation fails on macOS/Windows (missing `ip` command) |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-08-11 |
Acceptance
- `_local_ipv4_addresses()` returns non-loopback IPv4 addresses without the `ip` binary (macOS/Windows path).
- Linux path unchanged: `ip` output still preferred when present.
Plan
Diagnosis: `_local_ipv4_addresses` runs `subprocess.run(["ip", "-4", "-o", "addr", "show"], check=False)`. When the `ip` executable is missing (macOS, Windows), `Popen` raises `FileNotFoundError` during exec — `check=False` only suppresses non-zero exit codes, not a missing binary — so the exception propagates through `env_init.run` and aborts `compass env configure`. The gethostname fallback below it is dead code in practice.
Fix: catch `FileNotFoundError` around the `subprocess.run` call and fall through to the existing `socket.getaddrinfo(socket.gethostname())` path. One small change; no behaviour change on Linux where `ip` exists.
Notes
Root cause: `_local_ipv4_addresses` ran `subprocess.run(["ip", …], check=False)`; a missing executable raises `FileNotFoundError` at exec time, which `check=False` does not suppress (it only tolerates non-zero exit codes). Linux always ships `ip`, so the gethostname fallback was dead code in practice until macOS/Windows runners hit it.
Fix: catch `OSError` around the call and fall through to the existing fallback, filtering the fallback results for loopback/link-local addresses (gethostname may resolve to e.g. Debian's 127.0.1.1). Verified: pytest suite `test_nats_certs.py` passes with and without the `ip` binary (3 tests); Linux behaviour unchanged.
Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
PRs
| PR | Title |
|---|---|
| #1959 | [ores.compass] Fix NATS cert IP discovery on macOS/Windows |
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Gethostname fallback only strips literal 127.0.0.1, not the loopback range (and 169.254.0.0/16 link-local) | nats_certs.py | Accepted | Fixed in d91537dd6 — filter with is_loopback/is_link_local; deterministic getaddrinfo test added |
| 2 | Only FileNotFoundError caught, not other exec failures (PermissionError) | nats_certs.py | Accepted | Fixed in e511d2a0a — catch OSError instead |
Result
Shipped in PR #1959: `_local_ipv4_addresses` now catches `OSError` (covering missing and unusable `ip` binaries) and filters fallback addresses for loopback/link-local, so `compass env configure` completes on macOS/Windows and no non-routable address leaks into the NATS server cert SAN. Acceptance met: fallback path returns non-loopback IPv4 without `ip` (3 tests, 110-suite green); Linux path unchanged.