cd /news/developer-tools/a-port-number-is-not-an-identity · home topics developer-tools article
[ARTICLE · art-113522] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

A Port Number Is Not an Identity

A developer built Dev Server Activity, a native SwiftUI app for macOS that identifies and stops local development servers by grouping listening sockets by process and showing process context. The app fails closed when process identity is uncertain, re-verifying the PID's command line and listening ports before sending SIGTERM or SIGKILL. It is distributed outside the Mac App Store due to sandbox restrictions.

read4 min views1 publishedAug 27, 2026

Earlier this week, I opened a small Mac app I had built for myself and found at least 12 local development servers still running.

I had been moving quickly among projects, worktrees, terminals, and AI coding tools. Starting the next local app had become nearly frictionless. Remembering everything I had started had not.

The awkward part was not that macOS lacked the information. lsof

can show listening ports. ps

can show process arguments. The awkward part was reconstructing enough identity to answer three practical questions:

That last question changed how I built the tool.

If I see port 5173

, I can make an educated guess that it may be Vite. That does not tell me which Vite project it belongs to. A PID is more specific, but it still does not explain the project or command. A browser tab may show the local URL, but not whether the backing process is still needed by another worktree.

Before stopping anything, I wanted one view containing:

That became Dev Server Activity, a native SwiftUI app for macOS.

The app looks for likely local development servers owned by the current user, groups listening sockets by process, and shows the process context beside the stop controls. It recognizes common Node.js, Vite, Next.js, Python, Ruby, PHP, Bun, and Deno processes. When full process inspection is unavailable, it can still probe a fixed set of common localhost ports, but those results are marked as port-only and cannot be stopped from the app.

The read-only fallback is deliberate. If the app cannot establish process identity, it does not pretend that a port number is enough.

A process monitor has a time-of-check/time-of-use problem.

The app scans the machine and renders a row. Then a person reads the row and decides what to do. Between those events, the original process can exit. macOS can eventually reuse its PID. A stale interface should not become permission to signal whatever happens to occupy that PID later.

So the stop path does not trust the scan result by itself.

Immediately before sending either SIGTERM

or SIGKILL

, Dev Server Activity checks the target again:

1. Read the current command line for the selected PID.
2. Require an exact match with the command line shown during the scan.
3. Read the PID's current TCP listening sockets.
4. Require that it still owns at least one expected listening port.
5. Only then send the selected signal.

In the implementation, those checks are equivalent to asking ps

for the PID's current arguments and lsof

for the PID's current listening TCP sockets. If the command changed, the expected listener disappeared, or either validation cannot be completed, the app sends nothing and refreshes the list.

That is the behavior I wanted from a shutdown tool: when identity becomes uncertain, fail closed.

Normal Stop sends SIGTERM

, giving a process the opportunity to shut down cleanly. Force Stop sends SIGKILL

, which can end a process without allowing it to save state or perform cleanup.

Both actions require confirmation. Force Stop is intentionally presented as the more serious choice. The interface does not automate cleanup or make the decision on the user's behalf.

I wanted less reconstruction, not less judgment.

The full app needs current-user process and listening-port information so it can identify and stop a selected server. The Mac App Sandbox restricts that access, so the complete version is distributed directly rather than through the Mac App Store.

The current Apple-silicon release requires macOS 14 or later. It is signed with a Developer ID certificate, notarized by Apple, and ships with a SHA-256 checksum. The app has no accounts, analytics, advertising, telemetry, cloud service, privileged helper, or administrator prompt.

This began as a personal utility. I open it near the end of a coding session, see what is still alive, and stop only what I recognize and no longer need.

The 12-server surprise convinced me the workflow might be useful to other people moving among Cursor, Claude Code, Codex, terminals, worktrees, and several local services. Rather than turn that hunch into a prevalence claim, I published the source and the working release.

The project is MIT licensed. I would especially value reports about two things:

View the source or download Dev Server Activity

The app exists because I found 12 local servers running and did not know they were all there. The engineering lesson was more useful than the surprise:

A port number is not an identity, and a stale row is not permission to kill a process.

── more in #developer-tools 4 stories · sorted by recency
── more on @dev server activity 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-port-number-is-not…] indexed:0 read:4min 2026-08-27 ·