{"slug": "a-port-number-is-not-an-identity", "title": "A Port Number Is Not an Identity", "summary": "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.", "body_md": "Earlier this week, I opened a small Mac app I had built for myself and found at least 12 local development servers still running.\n\nI 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.\n\nThe awkward part was not that macOS lacked the information. `lsof`\n\ncan show listening ports. `ps`\n\ncan show process arguments. The awkward part was reconstructing enough identity to answer three practical questions:\n\nThat last question changed how I built the tool.\n\nIf I see port `5173`\n\n, 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.\n\nBefore stopping anything, I wanted one view containing:\n\nThat became **Dev Server Activity**, a native SwiftUI app for macOS.\n\nThe 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.\n\nThe read-only fallback is deliberate. If the app cannot establish process identity, it does not pretend that a port number is enough.\n\nA process monitor has a time-of-check/time-of-use problem.\n\nThe 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.\n\nSo the stop path does not trust the scan result by itself.\n\nImmediately before sending either `SIGTERM`\n\nor `SIGKILL`\n\n, Dev Server Activity checks the target again:\n\n```\n1. Read the current command line for the selected PID.\n2. Require an exact match with the command line shown during the scan.\n3. Read the PID's current TCP listening sockets.\n4. Require that it still owns at least one expected listening port.\n5. Only then send the selected signal.\n```\n\nIn the implementation, those checks are equivalent to asking `ps`\n\nfor the PID's current arguments and `lsof`\n\nfor 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.\n\nThat is the behavior I wanted from a shutdown tool: when identity becomes uncertain, fail closed.\n\nNormal **Stop** sends `SIGTERM`\n\n, giving a process the opportunity to shut down cleanly. **Force Stop** sends `SIGKILL`\n\n, which can end a process without allowing it to save state or perform cleanup.\n\nBoth 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.\n\nI wanted less reconstruction, not less judgment.\n\nThe 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.\n\nThe 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.\n\nThis 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.\n\nThe 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.\n\nThe project is MIT licensed. I would especially value reports about two things:\n\n[View the source or download Dev Server Activity](https://github.com/joeyarcisz/dev-server-activity)\n\nThe 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:\n\n**A port number is not an identity, and a stale row is not permission to kill a process.**", "url": "https://wpnews.pro/news/a-port-number-is-not-an-identity", "canonical_source": "https://dev.to/joeyarcisz/a-port-number-is-not-an-identity-g33", "published_at": "2026-08-27 20:22:07+00:00", "updated_at": "2026-08-27 20:49:18.588247+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["Dev Server Activity", "SwiftUI", "macOS", "Apple"], "alternates": {"html": "https://wpnews.pro/news/a-port-number-is-not-an-identity", "markdown": "https://wpnews.pro/news/a-port-number-is-not-an-identity.md", "text": "https://wpnews.pro/news/a-port-number-is-not-an-identity.txt", "jsonld": "https://wpnews.pro/news/a-port-number-is-not-an-identity.jsonld"}}