Show HN: Sonar – A tiny CLI to see and kill whatever's running on localhost (github.com)

by raskrebs 77 comments 204 points
Read article View on HN

77 comments

[−] raskrebs 57d ago
I always have a bunch of local projects running, particularly during the weekend where I'm rarely working on one thing at a time. A big pain of mine was constantly running into port: Redis from one project blocking another, orphaned dev servers from old worktrees, Docker containers I forgot about. The usual fix is lsof -iTCP | grep ..., then figuring out what the PID actually is, then killing it. But I always forget the command, and it doesn’t really include all the information that I like.

So I built this lightweight CLI. Single binary, no dependencies. It shows everything listening on localhost with process names, Docker container info, clickable URLs etc.

Sure there are workarounds, but none that satisfied my need for a short, easily rememberable command. Also nothing really has the same satisfaction as running sonar kill 3000 — it just feels nice. I’ve already been approached by a few agent orchestration tools that have been struggling with the same thing. It's really useful when you have multiple agents running, but it's not built for just that use case, I have also find it handy when killing off all containers after a failed cleanup and so on. Also know that MCPs are dead and CLIs are the new thing in agentic coding, this might be a useful tool for Claude, particularly when a compose process exits before all containers are stopped.

Open for contributions, ideas and feedback.

[−] embedding-shape 57d ago

> I’ve already been approached by a few agent orchestration tools that have been struggling with the same thing

Wow, this says more about the agent orchestration tool ecosystem than what you might think, that they're unable to kill child processes they themselves spawn makes it seem like they have zero clue about what they're doing.

Probably why my impression always end up with "Wow, what a vibe-coded mess" when I look through the source of all these harnesses, they don't seem engineered at all.

[−] msdz 57d ago

> that they're unable to [manage and] kill child processes they themselves spawn makes it seem like they have zero clue about what they're doing.

Yeah, at the bare minimum these projects could also use something like portless[1] which literally maps ports to human- (and language model-)readable, named .localhost URLs. Which _should_ heavily alleviate assignment of processes to projects and vice versa, since at that point, hard-to-remember port numbers completely leave the equation. You could even imagine prefixing them if you've got that much going on for the ultimate "overview", like project1-db.localhost, project1-dev.localhost, etc.

[1] https://port1355.dev/

[−] embedding-shape 56d ago
Well, or just use port 0 like we've done for decades, read what port got used, then use that. No more port collisions ever. I thought most people were already aware of that by now, but judging from that project even existing, seems I was wrong.
[−] hackerthemonkey 55d ago
That’s a little different, right? Using port 0 would imply that clients have not hard coded what port they should connect to and also we don’t mind having duplicate processes occupying other ports which are no longer on active use
[−] rtpg 56d ago
This is a stupidly annoying problem because it's _very easy_ to accidentally spawn children that won't get killed up in many kill situations because the distinction between processes and process groupes papered over by the fact that shells will be nice enough to kill via process group.

But if your program is some TUI in raw mode its ctrl+c handler is often just killing itself... leaving its children along! Process groups in Unix are a stupid mess and the default being "a process can go away with the subprocess sticking around" rather than the inverse has just caused so many of these long-standing issues.

[−] raskrebs 57d ago
[dead]
[−] pluc 57d ago
[−] 0cf8612b2e1e 57d ago
Sold on the name alone. It also has the API I never realized I needed

  puts 'usage:'
  puts 'murder 123    # kill by pid'
  puts 'murder ruby   # kill by process name'
  puts 'murder :3000  # kill by port'
[−] raskrebs 57d ago
Will check it out
[−] quotemstr 57d ago
Christ Almighty I hate our industry practice of binding to some inscrutable port number on localhost. Unix domain sockets aren't that hard! They're secure against all sorts of attacks and more convenient to boot. Instead of connecting to a number, you connect to a file. An ordinary file, with an ordinary name you can mv, chmod, and rm. Boring on a good way.

So why doesn't everyone run local services over Unix sockets?

The only problems: 1) web browsers don't support AF_UNIX URI scheme, and 2) ancient versions of Java don't have built-in APIs for AF_UNIX sockets.

That's it. For these trivial reasons, we've beat our head against arbitrary opaque numbers for decades.

And so, for want of a nail, the Unix was lost.

[−] 0x457 57d ago
Some random daemon binding to 3000 because it's the express default drives me nuts. I either do a Unix socket, a pick any random port if it has to bind on a port.
[−] formerly_proven 57d ago

> So why doesn't everyone run local services over Unix sockets?

> The only problems:

3) 40 years of Windows not supporting UDS.

[−] mustafa0x 57d ago
Nice util. It should be possible to kill inline.

I like clack/prompts. See its multiselect API.

https://github.com/bombshell-dev/clack/tree/main/packages/pr...

[−] maciejj 57d ago
Nice! I always forget the lsof flags and end up googling them every time. Would be cool if it could run in the system tray and show what's running on your ports at a glance. Also, the name had me thinking SonarQube at first, might be worth considering a rename to avoid the confusion.
[−] klaushardt 57d ago
Would be nice to have a flag to customize the URL displayed for Docker containers. I connect to my host via Tailscale, but I can’t open links with localhost. It would be helpful to have a parameter that allows us to choose a network device or specify an IP address to display.

    3000    wud (getwud/wud:latest)                            wud          getwud/wud:latest                       3000    http://localhost:3000
    3001    dockhand (fnsys/dockhand:latest)                   dockhand     fnsys/dockhand:latest                   3000    http://localhost:3001
[−] mrbonner 57d ago
Hey, thanks for sharing this. Your app inspires me to take a look at Go, again! I've been searching for another primary language to learn. My primary used to be Java at $day$ job and now Python for ML/AI. I love Python but still feel insecure given the lack of static typing. I look at TypeScript as well, especially in the context of Bun runtime. I decided it may not be for me, not the language, but the ecosystem around it.
[−] chwzr 57d ago
i have this in my .zshrc which provides same functionality:

  lk() {
    if [ $# -eq 0 ]; then
        local output=$(sudo lsof -iTCP -sTCP:LISTEN -n -P)
    elif [ $# -eq 1 ]; then
        local output=$(sudo lsof -iTCP -sTCP:LISTEN -n -P | grep -i --color=always $1)
    else
        echo "find and kill processes listening on ports. Usage: lk [pattern]"
        return 1
    fi

    if [ -z "$output" ]; then
        echo "No listening processes found."
        return 0
    fi

    # Show header + results
    echo "$(sudo lsof -iTCP -sTCP:LISTEN -n -P | head -1)"
    echo "$output"
    echo ""

    # Extract unique PIDs (skip the header row if no grep was applied)
    local pids=($(echo "$output" | awk '{print $2}' | grep -E '^[0-9]+$' | sort -u))

    if [ ${#pids[@]} -eq 0 ]; then
        echo "No PIDs found."
        return 0
    fi

    echo "PIDs to kill: ${pids[*]}"
    echo -n "Kill these ${#pids[@]} process(es)? [y/N] "
    read -r confirm

    if [[ "$confirm" =~ ^[Yy]$ ]]; then
        for pid in "${pids[@]}"; do
            echo "Killing PID $pid..."
            sudo kill -9 $pid
        done
        echo "Done."
    else
        echo "Aborted."
    fi
  }
[−] fcoury 57d ago
We live in crazy times. I wanted to add a PID to the list for my personal use and since I use Rust way more than Go, I decided to one-shot one app, and Codex indeed one shotted it, wow.

https://github.com/fcoury/sonars

[−] oweiler 56d ago
https://github.com/helpermethod/pk/blob/main/pk

I have a tiny shell function to kill whatever is listening on that port

[−] clutchski 57d ago
[−] pdimitar 57d ago
I am absolutely installing this and starting to use it daily!

For the even less patient there's also this (not mine): https://github.com/jkfran/killport

[−] mfkrause 57d ago
I always find myself going through my zsh history for lsof. Will definitely check this out, seems interesting (even though I'm generally reluctant of installing third-party tools for such jobs).
[−] RonanSoleste 57d ago
I just have an alias in my .bashrc :)
[−] moezd 57d ago
Sonar as in SonarQube? That's an interesting choice for a name :)
[−] cyanydeez 56d ago
Should git relos with CLI tools have opencode skills directory?
[−] koinedad 57d ago
I run lsof a good amount. This is a great idea.
[−] kohexo 57d ago
Honestly, pretty cool. I was wondering if something like this existed. Right now I have scripts to kill the ports I use consistently to avoid issues when developing. Kudos!
[−] FileForge 54d ago
Definitely useful
[−] Bradd3rs 57d ago
love this, i get tired of spamming lsof -i tcp:xxxx
[−] frankdenbow 57d ago
love this, happens too often
[−] jkestner 57d ago
I read the readme. :) Very nice. Thoughtful features. Get this on Homebrew!