Skip to content

owner: never name user@NNNN as a listener's unit - #16

Merged
pyjeebz merged 2 commits into
mainfrom
fix/no-user-session-unit
Nov 3, 2026
Merged

pyjeebz merged 2 commits into
mainfrom
fix/no-user-session-unit

Conversation

@pyjeebz

@pyjeebz pyjeebz commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Closes #5.

When another user's listener can't be tied to a process ID, ports names it from that user's processes. If they all run under one systemd service, the row reads systemd: <unit> and the stop hint is sudo systemctl stop <unit>.

A user's own processes outside any real service, such as systemd --user itself or desktop apps in app.slice/*.scope, sit directly under the session manager, user@1000.service. That was the innermost .service on their cgroup path, so the row read systemd: user@1000. ports then recommended sudo systemctl stop user@1000, which logs that user out and kills everything they run. It's the worst advice a stop path can give.

serviceUnit now never returns user@N. A process directly under the session manager belongs to no service.

  • Rows that used to read systemd: user@N go through the usual tie-break: a single known server for the port names the row, and anything else stays ? with user: <name>. That's the documented "ambiguous stays ?" rule.
  • Real user services inside the manager (user@1000.service/app.slice/foo.service) still name themselves. On cgroup v1 the undelegated pids line stops at user@1000.service and comes first, so that line is now skipped rather than deciding the answer.

How it was verified

Gate, locally with CI's versions: unit ✓, build ✓, integration ✓ (uncached), gofmt ✓, vet ✓, lint ✓ (v2.13.2).

TestServiceUnit has four new cgroup inputs, all failing on main:

0::/…/user@1000.service/init.scope                               main: user@1000   now: ""
0::/…/user@1000.service/app.slice/app-x.scope                    main: user@1000   now: ""
12:pids:/…/user@1000.service/app.slice                           main: user@1000   now: ""
12:pids:/…/user@1000.service  +  1:name=systemd:/…/foo.service   main: user@1000   now: foo

The existing cases (mysql, systemd-resolved, a user service inside user@1000, a session scope, nginx on v1) are unchanged.

Not verified live. This environment has no systemd, so the evidence is the cgroup-format tests. On a systemd host, sudo -u nobody-style listeners exercise it.

Risk

Tier: risky (what a stop recommends).

  • Public surface: rows that read systemd: user@N now read ?/user: <name>, or a real server via the tie-break. Their unit JSON field goes away (it's optional, already absent on most rows). The stop hint becomes sudo ports free N instead of sudo systemctl stop user@N.
  • Blast radius: Unit is set only in attribute() from serviceUnit. Its readers (the stop dedupe key, stopFix, kill.go, doctor's owner finding, the unowned-row dedupe) all handle an empty unit already, and none does anything worse than before.
  • Rollback: revert the squash commit.

A fresh-context review said ok to ship. Its one should-fix, the v1 line order, is the second commit. It also checked that skipping only the user@ prefix is the right scope: user-runtime-dir@ has no processes, getty@ hands off to a session scope, and no standard unit starts with user@ apart from the manager. Its same-class finding about nspawn containers is filed as #15.

Notes for the release

ports never suggests systemctl stop user@NNNN for another user's listener. Those rows stay ? with their owner unless a single known server explains them.


Generated by Claude Code

@pyjeebz
pyjeebz merged commit ad052b2 into main Nov 3, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

owner: the systemd hint can suggest systemctl stop user@NNNN (a whole user session)

2 participants