<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joey Arcisz</title>
    <description>The latest articles on DEV Community by Joey Arcisz (@joeyarcisz).</description>
    <link>https://dev.to/joeyarcisz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4096988%2F6c8cdb98-92d1-483b-8db8-a84d4b6fef3e.png</url>
      <title>DEV Community: Joey Arcisz</title>
      <link>https://dev.to/joeyarcisz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joeyarcisz"/>
    <language>en</language>
    <item>
      <title>A Port Number Is Not an Identity</title>
      <dc:creator>Joey Arcisz</dc:creator>
      <pubDate>Thu, 27 Aug 2026 20:22:07 +0000</pubDate>
      <link>https://dev.to/joeyarcisz/a-port-number-is-not-an-identity-g33</link>
      <guid>https://dev.to/joeyarcisz/a-port-number-is-not-an-identity-g33</guid>
      <description>&lt;p&gt;Earlier this week, I opened a small Mac app I had built for myself and found at least 12 local development servers still running.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The awkward part was not that macOS lacked the information. &lt;code&gt;lsof&lt;/code&gt; can show listening ports. &lt;code&gt;ps&lt;/code&gt; can show process arguments. The awkward part was reconstructing enough identity to answer three practical questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which project owns this listener?&lt;/li&gt;
&lt;li&gt;Do I still need it?&lt;/li&gt;
&lt;li&gt;Am I looking at the same process now that I was looking at when I decided to stop it?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last question changed how I built the tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The terminal had facts, but not the decision view
&lt;/h2&gt;

&lt;p&gt;If I see port &lt;code&gt;5173&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;Before stopping anything, I wanted one view containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the project folder;&lt;/li&gt;
&lt;li&gt;the full command line;&lt;/li&gt;
&lt;li&gt;the process name and PID;&lt;/li&gt;
&lt;li&gt;every listening host and port associated with that process;&lt;/li&gt;
&lt;li&gt;an explicit choice between a normal stop and a force stop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That became &lt;strong&gt;Dev Server Activity&lt;/strong&gt;, a native SwiftUI app for macOS.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The stale-row problem
&lt;/h2&gt;

&lt;p&gt;A process monitor has a time-of-check/time-of-use problem.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So the stop path does not trust the scan result by itself.&lt;/p&gt;

&lt;p&gt;Immediately before sending either &lt;code&gt;SIGTERM&lt;/code&gt; or &lt;code&gt;SIGKILL&lt;/code&gt;, Dev Server Activity checks the target again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the implementation, those checks are equivalent to asking &lt;code&gt;ps&lt;/code&gt; for the PID's current arguments and &lt;code&gt;lsof&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;That is the behavior I wanted from a shutdown tool: when identity becomes uncertain, fail closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop and Force Stop are different decisions
&lt;/h2&gt;

&lt;p&gt;Normal &lt;strong&gt;Stop&lt;/strong&gt; sends &lt;code&gt;SIGTERM&lt;/code&gt;, giving a process the opportunity to shut down cleanly. &lt;strong&gt;Force Stop&lt;/strong&gt; sends &lt;code&gt;SIGKILL&lt;/code&gt;, which can end a process without allowing it to save state or perform cleanup.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I wanted less reconstruction, not less judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it is a direct Mac download
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I made it open source
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The project is MIT licensed. I would especially value reports about two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;process patterns the detector misses or misidentifies;&lt;/li&gt;
&lt;li&gt;whether the project, command, PID, host, and port context is enough to make a stop decision confidently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/joeyarcisz/dev-server-activity" rel="noopener noreferrer"&gt;View the source or download Dev Server Activity&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A port number is not an identity, and a stale row is not permission to kill a process.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>opensource</category>
      <category>productivity</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
