Slack comes from the AUR and Toolbox from JetBrains' self-updating tarball, so neither is declared: the host installs official packages only, and a pinned archive would fight Toolbox's own updates. Slack's start and the operator's i3 window rules become one node-display-session contribution, because Slack's own launch-on-login is a symlink in ~/.config/autostart. Toolbox keeps its own autostart entry as its one start. The shared desktop.go header now names all four bundles.
slack
The Slack desktop app on the workstations, as a module (novox/hq ADR 0208). One Electron process is
both Slack's window and its tray icon. The module requires x11-display, so it is assigned only where
a display server is held on the same machine.
Owns
| what | where |
|---|---|
| Slack's one start at login, and its window rules | a config contribution to node-display-session (ADR 0212), which the seat's holder (i3) places in its configuration |
Nothing else. It holds no seat and writes no file.
- No package. Both workstations run
slack-desktop4.51.191-1, installed explicitly, from the AUR:pacmancounts it as foreign (no sync repository has it, and its packager is "Unknown Packager"). The host installs packages from the official repositories only, so the module does not declare it, and installing and upgrading Slack stays the operator's (an AUR helper, by hand).slack_statussays whether it is outside the official repositories, andslack_checksays when it is missing. - Why not a pinned archive (ADR 0205): Slack is a binary release of about 330 MB under Slack's own licence. ADR 0205 vendors free software the distribution lacks; redistributing Slack's binary from the mesh's store is not the module's to do. ADR 0205 does not apply, and the package stays as found.
- Slack's own files are found (ADR 0182):
~/.config/Slack/holds the sessions (cookies, local storage, the encryption key inLocal State), the settings (storage/root-state.json), the caches and the logs. The module never declares or writes any of it. The tools read only the settings' switches and the logs, and never the session files.
How it starts: the module's line in i3's configuration, and nothing else
One process has one starter (the rule picom states for the desktop modules). Slack's starter is
this module's contribution: exec --no-startup-id /usr/bin/slack --gtk-version=3 -s, which i3
runs once when the session starts. Those are the arguments of the package's own desktop entry. -s
starts Slack hidden, to the tray.
Why not an XDG autostart entry, as nextcloud-client and blueman start:
- Slack's own setting Launch app on login is the existence of
~/.config/autostart/slack.desktop. Slack reads the file's presence back into the setting at every start. Ticking the setting makes the file a symbolic link to/usr/share/applications/slack.desktop, and unticking it deletes the file. This is in Slack 4.51's own code, not a guess. - If the module owned that path as a file, two writers would hold it: the mesh writing it, and Slack deleting it whenever the setting is unticked.
- If the entry were left to Slack, the start would be a link that Slack makes in the operator's home, which this mesh's rules do not want there.
- A contribution is a start the mesh owns, beside the window rules it belongs with, in the window
manager that runs it. Under sway,
swayholds the same seat with the same grammar.
Excluded, and named by slack_check as a second start:
- any
~/.config/autostart/slack.desktop: the predecessor's file, or Slack's link if Launch app on login is ticked again (untick it; Slack removes the link); - an
exec … slackof the operator's in~/.config/i3/config.d/; /etc/xdg/autostart/slack.desktop, which the package does not ship.
Not a start: systemd's XDG autostart generator makes a unit app-slack@autostart.service from the
entry while the entry exists. Only a desktop that starts xdg-desktop-autostart.target runs it, and
i3 does not. The unit goes when the entry goes.
Slack's cgroup does not say who started it. Slack moves its main process into a scope of its own
(app-slack-<pid>.scope) whoever starts it. slack_status therefore names the starts from the
configuration, not from the cgroup.
The window rules
The operator's ~/.config/i3/config.d/50-slack.conf (2026-08) became this contribution as it was:
- Slack's chat window goes to workspace 3 (
$ws3, i3's variable, in scope where the contributions are placed); - it is tiled, not floating;
- it has a 2-pixel border.
The criteria are those of the operator's file. They were verified live then, and the window tree of both workstations on 2026-10-05 still agrees:
- Slack owns four X windows. The only one i3 manages has the class
slackin lower case. - The three of class
Slack(the packaged entry'sStartupWMClass) are unmanaged helpers and the tray icon. A rule onclass="Slack"therefore matches nothing. window_role="browser-window"keeps a call or screen-share window out of the rules.
Once the module is assigned, the operator deletes 50-slack.conf (below). i3 accepts the same
for_window twice, so nothing breaks while both are there. But the rules belong in one place, and
slack_check names the file until it is gone.
Tools
They are served by the node's runtime as the operator account (ADR 0175), and are read-only except
restart. No answer carries a token, a cookie, a message, or the name of a person, channel or
workspace. Accounts and workspaces are counted, never named.
| tool | does |
|---|---|
slack_status (r) |
|
slack_log (r) |
the last lines (default 100, at most 2000) of Slack's main-process log (source: browser, across its rotations) or of the web app's console (source: webapp). problems: true keeps error and warn entries. Tokens (xox…), the d cookie, anything shaped like a credential, notification and message text, and the names of people, channels and workspaces become <hidden>. Answers are capped at 256 KiB |
slack_restart (a) |
ends Slack (SIGTERM, forced after 8 s) and starts /usr/bin/slack --gtk-version=3 -s in the operator's session. The start is a transient user unit mesh-slack, so it outlives the tools runtime, and its output goes to the journal. Answers the pids. Refused plainly when nobody is logged in to the desktop |
slack_check (r) |
|
The output check (EPIPE). A session started before the i3 module's login entry sent the
session's output to the journal (systemd-cat -t x-session) gives every program it starts a stdout
pipe whose reader is gone. An Electron app's write there fails with EPIPE, and an unhandled one is the
"write EPIPE" dialog. Slack's own logger works around it: it silences its console output on EPIPE. Any
other write still fails. So slack_check finds the pipe by its reader: it looks for a process of the
account holding the pipe open for reading (/proc/<pid>/fdinfo). It names it when there is none.
slack_restart cures it, and every later login does too.
Where the tools read:
- the processes, and the fd links and fdinfo of Slack's main process, in
/proc(the account's own only); - the settings' switches, from
~/.config/Slack/storage/root-state.json, and the Electron version fromlocal-settings.json; - the logs in
~/.config/Slack/logs/default/. The accounts are counted from theSTORE_USER_WORKSPACESentries since the lastINITIALIZE; - the window tree from
xwininfo -root -tree, with the session'sDISPLAY. The session is found from the window manager's own environment, as the other desktop modules find it; - the notifier from
busctl --user list.
Every command has a timeout and capped output. Everything runs through an injected runner, a fake root and a fake link reader in the tests.
What changes when it is assigned
| g14 | shanks | |
|---|---|---|
| package | none: slack-desktop 4.51.191-1, AUR, installed explicitly |
the same |
| start | i3's configuration gains the module's start. Until the predecessor's ~/.config/autostart/slack.desktop is deleted, the next login starts Slack twice. The second start hands over to the first and exits, because Slack is single-instance. Running now: started by dex from that entry at the login of 2026-10-04 16:26 |
the same entry, the same until deleted. Running now: since 2026-10-04 17:05, with its stdout on a pipe nobody reads (that session predates the i3 module's login entry), so slack_check names it until slack_restart or the next login |
| window rules | i3's configuration gains them. The operator's 50-slack.conf repeats them until deleted |
the same |
| settings | Launch app on login reads on (the entry exists), start hidden, run from the tray, default notifications (to dunst); 1 account, 1 workspace | the same switches |
| tray, notifications | icon in i3bar's tray; dunst owns the notification name | the same |
Migration (ADR 0182), on each workstation, once the module is assigned and pushed
- Delete
~/.config/autostart/slack.desktop. It is the predecessor's file (its comment names the retired desktop module), the second start. At its next start Slack reads Launch app on login as off. Leave the setting off: ticking it makes the entry again, andslack_checknames it. - Delete
~/.config/i3/config.d/50-slack.conf. It is the operator's own file, and its rules are now the module's. - shanks only: run
slack_restart, or log out and in, so that Slack's output is read.
slack_check then answers ok. Deleting both files before the module is assigned would leave Slack
without a start and without its rules until it is.
Leaves as found
~/.config/Slack/: the sessions, the settings, the caches, the logs, more than a dozen.org.chromium.Chromium.*leftovers andStaleCookies-*files from past upgrades. They are Slack's to keep and the operator's to delete.- The package and its desktop entry,
/usr/share/applications/slack.desktop. - The
x-scheme-handler/slackdefault, which is thexdgmodule's.
Relies on
i3(the holder ofnode-display-session), which places the contribution. The contribution is a dependency on that seat (ADR 0210 §3). Assigningslackwhere no module holds it is refused.dunst(the holder ofnode-notifier) for Slack's notifications, which Electron sends toorg.freedesktop.Notifications. Nothing in the mesh declares this dependency, because the module makes no contribution to that seat.slack_checkreports a missing notifier instead.- A tray in the bar (i3bar's
tray_output). Without one, Slack runs with no icon, and closing its window leaves it unreachable.slack_checkreports it. xwininfo(xorg-xwininfo) for the tray question. No module declares it, and this one does not install it for a status line. Without it,slack_statusanswers the tray as unknown.- A display server on the same machine (
x11-display, ADR 0208 §3).