Session-restored windows start outside bypass #113

Closed
opened 2026-08-20 23:23:05 -04:00 by mysticalsoap · 3 comments
Owner

Split out of #67 (PR #112). KDE session restore relaunches apps from the command line recorded at session save, not by resolving desktop entries — so a listed app restored at login starts outside the bypass cgroup, and stays out until manually restarted through any covered path.

This is inherent to deterministic launch-time placement: session restore passes through no launcher, so there is nothing to shim. Related same-family gap, already documented in the README: kglobalaccel captures each shortcut's KService at registration (verified against kglobalacceld's KServiceActionComponent, which stores the KService::Ptr at construction), so global-shortcut launches pick up list changes only at the next login.

Candidate directions, none pretty:

  • Accept as a documented limitation (current state — README notes it).
  • A deterministic sweep at bypass activation: autostarted/restored apps run in well-named systemd user units (app-<id>@autostart.service, session-restore scopes), so their pids are discoverable without process-name guessing and could be migrated into the cgroup when it appears. This is migration-after-the-fact rather than launch-time placement, but driven by unit names, not a process watcher — worth weighing against the doctrine if the gap turns out to matter in practice.
  • Upstream: kglobalaccel re-resolving services on sycoca change would fix the shortcut half properly; could be reported to KDE.
Split out of #67 (PR #112). KDE session restore relaunches apps from the command line recorded at session save, not by resolving desktop entries — so a listed app restored at login starts outside the bypass cgroup, and stays out until manually restarted through any covered path. This is inherent to deterministic launch-time placement: session restore passes through no launcher, so there is nothing to shim. Related same-family gap, already documented in the README: kglobalaccel captures each shortcut's KService at registration (verified against kglobalacceld's `KServiceActionComponent`, which stores the `KService::Ptr` at construction), so global-shortcut launches pick up list changes only at the next login. Candidate directions, none pretty: - Accept as a documented limitation (current state — README notes it). - A deterministic sweep at bypass activation: autostarted/restored apps run in well-named systemd user units (`app-<id>@autostart.service`, session-restore scopes), so their pids are discoverable without process-name guessing and could be migrated into the cgroup when it appears. This is migration-after-the-fact rather than launch-time placement, but driven by unit names, not a process watcher — worth weighing against the doctrine if the gap turns out to matter in practice. - Upstream: kglobalaccel re-resolving services on sycoca change would fix the shortcut half properly; could be reported to KDE.
Author
Owner

Recording the full watcher assessment from the PR #112 discussion, since this issue is where sweep-vs-watcher gets decided. To be precise about history first: process watching was never banned on principle — ProtonVPN's implementation was found unstable (dropped fork events from lossy perf buffers), and that instability is this fork's origin story. The question worth answering is whether a careful watcher would be sound. It wouldn't, for three separate reasons:

1. The Proton failure is mitigable — this part is fixable. Perf buffers drop silently, but the newer BPF ring buffer and the netlink proc connector both make drops detectable (failed reserve / ENOBUFS), so an implementation can rescan /proc on drop and become eventually consistent. Cost: eBPF toolchain, extra capabilities, and kernel-version sensitivity inside a root service that is currently plain Python.

2. The socket-tagging race is not mitigable. cgroup v2 associates a socket with its creator's cgroup at socket creation; migrating the process later does not retag existing sockets. A watcher classifies after exec by definition, so any connection opened before the migration stays classified the old way for its whole life — and long-lived connections opened at startup are exactly what chat clients, torrent clients and launchers do first. Launch-time placement (join-then-exec) is immune by construction: the app never runs an instruction outside the cgroup, so every socket it ever opens is tagged right. This is the structural reason, independent of any implementation quality.

3. Classification is guesswork at runtime. A watcher sees a pid and a binary path and must decide "is this a listed app?" — the same fuzzy Exec-to-entry matching rejected elsewhere, but system-wide, continuous, and consequential in both directions.

Conclusion: a fixed watcher buys a bigger promise it structurally can't keep ("most traffic, after the first milliseconds, usually"), versus the current smaller promise kept 100% of the time. For the residual gaps this issue tracks, the unit-name sweep at bypass activation recovers most of the value with none of the watching — with the honest caveat that it shares the pre-existing-socket limitation from point 2, which is why launch-time coverage stays primary and the sweep could only ever be a safety net.

Recording the full watcher assessment from the PR #112 discussion, since this issue is where sweep-vs-watcher gets decided. To be precise about history first: process watching was never banned on principle — ProtonVPN's implementation was found unstable (dropped fork events from lossy perf buffers), and that instability is this fork's origin story. The question worth answering is whether a *careful* watcher would be sound. It wouldn't, for three separate reasons: **1. The Proton failure is mitigable — this part is fixable.** Perf buffers drop silently, but the newer BPF ring buffer and the netlink proc connector both make drops *detectable* (failed reserve / ENOBUFS), so an implementation can rescan /proc on drop and become eventually consistent. Cost: eBPF toolchain, extra capabilities, and kernel-version sensitivity inside a root service that is currently plain Python. **2. The socket-tagging race is not mitigable.** cgroup v2 associates a socket with its creator's cgroup *at socket creation*; migrating the process later does not retag existing sockets. A watcher classifies after exec by definition, so any connection opened before the migration stays classified the old way for its whole life — and long-lived connections opened at startup are exactly what chat clients, torrent clients and launchers do first. Launch-time placement (join-then-exec) is immune by construction: the app never runs an instruction outside the cgroup, so every socket it ever opens is tagged right. This is the structural reason, independent of any implementation quality. **3. Classification is guesswork at runtime.** A watcher sees a pid and a binary path and must decide "is this a listed app?" — the same fuzzy Exec-to-entry matching rejected elsewhere, but system-wide, continuous, and consequential in both directions. Conclusion: a fixed watcher buys a bigger promise it structurally can't keep ("most traffic, after the first milliseconds, usually"), versus the current smaller promise kept 100% of the time. For the residual gaps this issue tracks, the unit-name sweep at bypass activation recovers most of the value with none of the watching — with the honest caveat that it shares the pre-existing-socket limitation from point 2, which is why launch-time coverage stays primary and the sweep could only ever be a safety net.
Author
Owner

Decision: this lands in 0.9.1, not later — if bypass is a headline feature of the release, the feature should be as good as reasonable, so the known hole gets closed in the same release. Ships as its own PR after #112 merges (one mechanism per PR).

Settled direction, per the analysis above: a deterministic sweep, no watching.

  • Trigger points: bypass activation (the moment the cgroup exists), and list edits while bypass is up.
  • Enumeration: the systemd user manager's app units embed the desktop id in their names (app-@autostart.service, app-@.service, app--.scope) — map unit names to listed apps' desktop ids, then move the member pids of matching units into the cgroup. Pure name matching against systemd's own records; nothing inspects or guesses at processes.
  • Reach: this closes more than session restore — it also catches the autostart login race (belt to --wait's suspenders) and mid-session launches through kglobalaccel's stale-entry cache (those land in app-@.service units too).
  • Carried caveat: sockets opened before the sweep keep their old classification (cgroup v2 tags at socket creation), so the sweep is a safety net, not a guarantee — launch-time placement stays primary and the README's restart advice stays.
Decision: this lands in 0.9.1, not later — if bypass is a headline feature of the release, the feature should be as good as reasonable, so the known hole gets closed in the same release. Ships as its own PR after #112 merges (one mechanism per PR). Settled direction, per the analysis above: a deterministic sweep, no watching. - **Trigger points**: bypass activation (the moment the cgroup exists), and list edits while bypass is up. - **Enumeration**: the systemd user manager's app units embed the desktop id in their names (app-<id>@autostart.service, app-<id>@<hex>.service, app-<id>-<pid>.scope) — map unit names to listed apps' desktop ids, then move the member pids of matching units into the cgroup. Pure name matching against systemd's own records; nothing inspects or guesses at processes. - **Reach**: this closes more than session restore — it also catches the autostart login race (belt to --wait's suspenders) and mid-session launches through kglobalaccel's stale-entry cache (those land in app-<id>@<hex>.service units too). - **Carried caveat**: sockets opened before the sweep keep their old classification (cgroup v2 tags at socket creation), so the sweep is a safety net, not a guarantee — launch-time placement stays primary and the README's restart advice stays.
Author
Owner

Correcting my earlier comment on this issue: I wrote that the sweep would also catch "launches through KDE's per-session shortcut cache". That is only true for launches that precede the next activation. The sweep runs at bypass activation and deliberately not continuously, so a shortcut launch during an already-active session is caught by nothing until bypass re-activates.

The real fix for that path is re-login — kglobalaccel then resolves the .desktop override and the launch is placed at exec time, which is the guarantee that actually holds (see socket tagging: migration after the fact never retags existing sockets). Verified live on a session where Ctrl+Alt+T kept launching unwrapped konsole while menu launches were correctly bypassed.

Scope as implemented: the sweep covers apps already running when bypass activates — session restore, and autostart entries that beat activation despite --wait. Nothing else.

Correcting my earlier comment on this issue: I wrote that the sweep would also catch "launches through KDE's per-session shortcut cache". That is only true for launches that precede the next activation. The sweep runs at bypass activation and deliberately not continuously, so a shortcut launch during an already-active session is caught by nothing until bypass re-activates. The real fix for that path is re-login — kglobalaccel then resolves the .desktop override and the launch is placed at exec time, which is the guarantee that actually holds (see [[socket tagging]]: migration after the fact never retags existing sockets). Verified live on a session where Ctrl+Alt+T kept launching unwrapped konsole while menu launches were correctly bypassed. Scope as implemented: the sweep covers apps already running when bypass activates — session restore, and autostart entries that beat activation despite --wait. Nothing else.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
mysticalsoap/aqomui#113
No description provided.