Service kinds
A .test name can point at very different things: a process LocalRun started, a dev server you
started in a terminal, a database managed by launchd, or a Docker port forward. What you are
allowed to do differs in each case, and doing the wrong thing has real consequences: signalling a launchd-supervised process, for example, just makes it restart and look unkillable.
So LocalRun classifies every listening service and the daemon decides which controls are offered. The tray renders what it is told. A greyed-out button always has a reason behind it, and hovering it shows you that reason.
The four kinds
| Kind | What it is | Start / stop |
|---|---|---|
project | LocalRun started it | Full control, logs captured |
adopted | Something you started; LocalRun only proxies to it | Stop kills the process tree; start needs a run command |
system | launchd-supervised, or owned by another user | Routed through the supervisor |
proxy | Port-only entry, or a Docker port forward | Neither |
project
The normal case. You gave LocalRun a folder and a run command, so it spawned the process and is its parent. It has the output, so the Logs tab is live. Start, stop and restart all do exactly what they say.
adopted
You started something yourself (npm run dev in a terminal, say), and LocalRun is proxying a
.test name to it. It can stop that process, because stopping is just a matter of killing the tree.
It cannot show you logs, because the output went to your terminal, not to LocalRun.
Starting an adopted project again requires a run command. If LocalRun cannot work out how the process was launched, Start stays disabled and says so.
A shell-started dev server is usually not its own process-group leader, so signalling the group would either miss it or take out your shell. LocalRun walks the descendants by parent pid instead.
system
The port belongs to something launchd supervises, or to another user. LocalRun never signals these directly; launchd would simply restart the process and you would see a service that refuses to die.
Instead, stop and start are routed through the supervisor, using launchctl. A user-scoped service
needs no password. A root LaunchDaemon does, and macOS shows its own native authorisation prompt;
the button tells you in advance that it will ask.
proxy
Either a port-only entry with nothing else known about it, or a docker-proxy process. Neither can
be started or stopped meaningfully; killing docker-proxy does not stop the container behind it,
so LocalRun refuses rather than pretending.
How the classification is made
None of the detection needs elevated privileges:
- If the process's parent is the daemon, it is a
project. launchctl listmaps a pid to a label for user services.- A root
LaunchDaemonis found by matching the executable path against the plists in/Library/LaunchDaemons/and reading the label withplutil. - A
docker-proxyexecutable marks the entry as aproxy.
What the daemon reports
Along with the kind, the daemon returns a capability set for each project: whether it can be stopped, started, restarted, whether logs exist, whether a terminal can be opened, whether the action will need a password, and a plain-English reason.
The practical upshot: if a button is disabled, it is because the daemon knows the action would not work: not because the UI is being cautious. Hover it and the tooltip tells you which case you are in.
Next: Keep running covers who supervises a project once it is started.