Skip to main content

fleet-port-audit

Why It Exists

local-dev-stack's own port-block invariant tells one repo how to pick a distinct 54N2x Supabase block and a distinct WEB_PORT/API_PORT pair. It says nothing about auditing an entire fleet of repos that grew that way independently, over time, without a shared allocation ledger. This skill is what filled that gap, after a full audit of a real multi-repo fleet found a live collision pileup: two repos defaulting to the identical WEB_PORT=3010/API_PORT=4010, a third repo's Supabase block byte-for-byte identical to an unrelated repo's, and one repo's web process silently binding a framework's own default port because its command line never referenced the env var that was supposed to control it.

Fixing that pileup took a full plan, six parallel implementation agents, and three rounds of architect review — the first two rounds each caught a real regression the mechanical fix had introduced (a stale API URL left pointing at a vacated port; a Supabase auth redirect URL that broke local OAuth after a web port moved; a gitignored .env.local file invisible to the sweep tool because grep on that machine was silently gitignore-aware). Every invariant in this skill traces back to one of those specific, real findings — including the discovery, weeks after the fleet was declared collision-free, that a "free" port was actually held by a root-owned launchd socket the whole time, invisible to anything short of sudo lsof.

What It Does

Two independent axes, audited and allocated separately: the app dev-server ports (WEB_PORT/API_PORT) each repo's web/api processes actually bind, and each repo's own local Supabase port block. A repo can be clean on one axis and colliding on the other — treating them as one problem is how the original pileup went unnoticed for as long as it did.

The fleet's actual port map — which repo owns which port, right now — is deliberately not shipped inside this skill package. It's a single structured YAML file the skill reads via an external, independently-updated source: $FLEET_PORT_REGISTRY if set, a fixed dotfiles-relative default if not, or an explicit ask-and-remember prompt if neither resolves. Baking a live, frequently-mutated fleet map into a redistributed skill package means every consumer gets a stale copy the moment anyone else's allocation changes.

How To Use It

skills add git@github.com:catesandrew/next-starters.git --skill skills/fleet-port-audit -g

Set FLEET_PORT_REGISTRY to a real YAML file (see references/registry-schema.yaml for the shape), or let the skill prompt you for a path the first time it runs. From there: "what port should <new-repo> use" allocates the next free pair on both axes from the registry's current maximum; "audit the fleet" sweeps every registered repo for drift between what's declared and what's actually read.

Gotchas & Invariants

The full 8 invariants live in SKILL.md. The two worth knowing before you run this against a real fleet for the first time:

  • A declared-correct port value proves nothing on its own. Check both where a port is declared (.env.dev) and where it's actually read (the process-compose/Procfile command line's --port/API_PORT= argument). This exact bug — a web command with no --port flag at all — shipped twice in the rollout that produced this skill, each time silently falling back to the framework's own default and colliding with an unrelated repo.
  • sudo lsof -i :<port> -P -n, never a bare lsof, before calling a port free. A root-owned listener — commonly a Docker Desktop or OrbStack port-forwarding proxy held via macOS launchd socket activation — is invisible to an unprivileged process list. This isn't theoretical: a port the fleet's own registry had marked free for weeks turned out to already be held this way, found only when a new repo's dev server refused to bind it.
  • local-dev-stack — the single-repo operational stack whose own port-block invariant is the origin of the convention this skill audits across the whole fleet. Companion, not a superset: local-dev-stack owns one repo's stack; this skill owns the cross-repo layer above it.