Repository navigation
v2.3.0
What's new in v2.3.0
A bug-hunting round: fuzzing, concurrent agents, corrupted stores, hostile
tools and the app's own lifecycle. Everything below was reproduced first.
Fixed
- Guarding a busy port killed the server on it. Watching or guarding a
port recorded its occupant in a different format from the one scans
write, so the next scan read the server already there as a newcomer: a
watch announced it, and a guard killed the very thing it was set to
protect. - One long argument froze every scan. The patterns that find a secret
in a command line backtracked over every way of splitting a run of word
characters: 800 characters took 2.6 seconds and a few kilobytes took
minutes, on every listener, on every scan. 500 KB now takes 0.15s. - Killing a port inside tmux killed the whole session. A tmux server
keeps the argv of the command that opened it, sotmux new -s build tsc --watchlooked like a reloader and the kill went to tmux and everything
under it: every pane, the editor, other servers. - A port held by another user read as free. The listener table cannot
see root's or_postgres's sockets, sofree 5432exited 0 and the
next server still could not bind. Kill, free, wait, reserve and exec now
ask the kernel before answering. - Kills reported more than they did. A partial kill exited 0 as
"killed"; a tree kill swallowed every child it could not stop (and its
pgrep fallback killed none at all); a process that had already exited
was recorded in History as killed;freechecked that the pid died
rather than that the port was free. - An agent installed through npm or pip looked like an ended session,
because it runs asnode …/bin/geminiorpython3 …/bin/aider. That
letkill --orphanedreap a live agent's server and allowed a rival
agent's kill that should have been refused. - Agent kills trimmed History to 50 entries however long you had set
it, and changing the length in Settings dropped everything an agent had
recorded since the window last read it. - One damaged entry hid every lease and every history row, and the
next write saved that empty list over the good ones. Entries are now
salvaged one at a time. - Four crashes: a non-finite JSON-RPC id, an oversized
CLAUDE_PID, a
closed stderr, and a stored lease naming an impossible pid. - The popover's shortcuts stayed live after it closed, so Return in
the Workbench started a kill from a list nobody could see, and Escape
stopped working everywhere. A confirmation opened from the popover fed
its own Return back and stacked a second dialog. - Cut, Copy, Paste, Select All, Undo and Close did nothing in every
PortNanny text field and window: a menu bar app still needs a main menu
for those keys to route. - The refresh timer stopped while any dialog or menu was open, blinding
the guard exactly then; the guard announced "Auto-killing" before
signalling anything and said nothing when a kill failed; refusal
banners never asked for notification permission, so macOS dropped them;
clicking a watch or guard banner did nothing;portnanny://showduring
launch was dropped, closing the welcome tour left nothing on screen, and
a second copy of PortNanny guarded every port twice. - Smaller ones:
free-port --rangealone was rejected, a lease reason
kept--token=…unredacted and quoted it back to other agents, a
process name with a carriage return could write its own row into
portnanny list, Docker containers publishing a port range went
unnamed, a test worker under a path with a space could not be killed,
portnanny://kill/9999/3000acted on :3000,agent-docsreplaced a
symlinked CLAUDE.md with a private copy, and a scan lost to a stalled
network mount stopped every later one.
Changed
- Exit codes: a partial kill is 4, a lease or history write that fails is
73, andexecanswers 126 and 127 like a shell. - MCP results say
isErrortruthfully: await_for_port_freethat timed
out is an error, an unknownlist_portsfilter is refused rather than
silently listing everything,kill_portvalidates its port and pid, and
list_portswith filtermineand no identity says so. - The shortcut recorder refuses ⌘W, ⌘Q and combinations macOS owns, which
it used to accept and then never fire. - Toasts and errors now appear in the Workbench and Settings; both were
drawn only by the popover.
📦 Install
Download PortNanny-2.3.0.dmg below, open it, and drag PortNanny to
Applications. Universal binary: runs natively on Apple Silicon and Intel;
requires macOS 13 (Ventura) or newer.
Not notarized (no Apple Developer account). First launch on macOS 15 or
newer: open it once, then System Settings → Privacy & Security → Open
Anyway. On macOS 13 and 14: right-click PortNanny.app → Open → Open.
Either way,xattr -dr com.apple.quarantine /Applications/PortNanny.app
skips the dialog. Homebrew (brew install --cask portnanny) does this for you.
What's Changed
- Bug hunt: crashes, the guard, hostile input, and what kills report by @mukes555 in #59
- Release 2.3.0 by @mukes555 in #60
Full Changelog: v2.2.1...v2.3.0