How Ports and Processes
Work on macOS
A plain-language walkthrough of TCP ports, process IDs, the kernel socket table, and how macOS decides which app is allowed to kill which process. Understanding this is the key to understanding why port collisions happen — and how to fix them.
1How ports work on macOS
A port is a 16-bit number (0–65535) that the operating system uses to route incoming network data to the right process. When your dev server starts, it calls thebind() syscall with a socket and a port number. The kernel records the mapping in an internal socket table: port 3000 → socket fd 7 → PID 91234.
Only one process can hold a given (address, port) pair at a time — that's a hard kernel rule. When a second process tries to bind the same port, the kernel returns error EADDRINUSE (error code 48 on macOS). Your process crashes. You typelsof -i :3000 and sigh.
The socket table lives in the kernel. Reading it requires syscalls like sysctlbyname("net.inet.tcp.pcblist", ...) that enumerate all open TCP connections along with their owning PIDs. This is how tools like lsof, netstat, and ss work — they're just wrappers around these kernel interfaces.
2How processes work on macOS
Every running program is a process with a unique PID (Process ID) assigned by the kernel. PIDs are ephemeral — they get recycled — but while a process lives, its PID is its identity in the kernel's process table.
macOS exposes process metadata through proc_info() and the sysctl()family of syscalls. These let you look up a PID's name, executable path, user ID, and open file descriptors. To terminate a process you call kill(pid, SIGTERM) (polite request) or kill(pid, SIGKILL) (force).
Authorization rule: kill() requires that your process's effective UID matches the target's UID, or that you're running as root. A regular user app can kill its own child processes freely. Killing any other process on the system — including the node process squatting on port 3000 — requires either matching UIDs or elevated privileges.
Portia runs as your user, so it can kill your own processes without needing root.
3What Portia actually does
Under the hood, Portia is a Launch Agent — a background daemon that runs continuously in your login session, consuming zero CPU while idle. Here's the exact sequence when a port collision occurs:
- The system log emits an
EADDRINUSEevent. Portia's Launch Agent, subscribed viaos_log, wakes up. - It calls
sysctlbyname("net.inet.tcp.pcblist")to get the full socket table and find which PID owns the contested port. - It calls
proc_info(PROC_PIDTBSDINFO, pid)to resolve the PID to a human-readable process name and path. - A native macOS
UNUserNotificationpops up: "node is blocking port 3000. Strike?" - You click Strike. Portia calls
kill(pid, SIGTERM). The process is gone.
Steps 2, 3, and 5 each require unrestricted kernel access. That's exactly what the App Store sandbox takes away.
4The App Store sandbox: what it actually blocks
Every app distributed through the Mac App Store must run inside Apple's App Sandbox. The sandbox is enforced at the kernel level via mandatory access control (MAC). It cannot be bypassed, worked around, or negotiated with at runtime — it's a compile-time entitlement that the kernel checks on every syscall.
Here's what the sandbox specifically blocks for Portia's use case:
- Reading the global socket table —
sysctlbyname("net.inet.tcp.pcblist")inside a sandbox returns only the calling process's own sockets. Portia can see its own connections but not thenodeprocess holding port 3000. - Inspecting other processes —
proc_info()andsysctl(CTL_KERN, KERN_PROC)on arbitrary PIDs return EPERM. A sandboxed app cannot look up a process it didn't spawn itself. - Killing external processes —
kill(pid, SIGTERM)on any process not in the app's own process group returns EPERM, even if the target has the same UID. Apple's sandbox denies this at the MAC policy layer, before the standard UNIX UID check even runs. - Installing a Launch Agent — Sandbox containers are isolated from
~/Library/LaunchAgents. A sandboxed app cannot write a plist there, so it cannot install itself as a persistent background daemon.
Can't Apple grant special entitlements? In theory, com.apple.security.temporary-exception.* entitlements exist for some edge cases — but Apple's review team explicitly rejects apps that use kill()on arbitrary PIDs. It's categorized as a security risk because a malicious app could use the same API to kill system processes or security software.
There is no entitlement path that would let a sandboxed app enumerate all open sockets and kill their owners. The capability simply does not exist in the App Store model.
5What Portia Lite can and can't do
Portia Lite runs in the App Store sandbox. It can detect when your own app crashes with EADDRINUSE and tell you which port was blocked. What it cannot do is look up who owns that port or offer to kill them — those syscalls are denied.
Think of Lite as a smoke alarm: it tells you there's a fire. Pro is the sprinkler system: it actually puts it out.
Get the full hunting experience
One-click kill. Full PID info. Background Launch Agent. No sandbox. Direct download, one-time purchase.