Files
mesh-catalog/modules/slack/README.md
T
jochen 75f25fca21 Add slack and jetbrains-toolbox: one start each, the apps kept as found
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.
2026-10-05 15:05:55 +02:00

12 KiB

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-desktop 4.51.191-1, installed explicitly, from the AUR: pacman counts 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_status says whether it is outside the official repositories, and slack_check says 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 in Local 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, sway holds 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 … slack of 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 slack in lower case.
  • The three of class Slack (the packaged entry's StartupWMClass) are unmanaged helpers and the tray icon. A rule on class="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)
  • whether Slack runs: the main process's pid, since and scope, and how many helper processes it has
  • the installed version, and whether it is outside the official repositories
  • where its output goes (fd 1 and 2): the journal, /dev/null, a pipe someone reads, or a pipe nobody reads
  • whether its icon sits in a tray, and whose (from the X window tree)
  • who owns the session bus's notification name (dunst)
  • the switches: launch on login, hide on start, run from the tray, the notification method, hardware acceleration; and the Electron version
  • how many accounts and workspaces it is signed in to since its last start, counted from the reports Slack logs
  • what starts it at login
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)
  • Slack is installed (if not: from the AUR, by the operator)
  • exactly one start: the module's line is in i3's configuration, with no autostart entry and no other exec
  • the window rules are placed, and no file of the operator's repeats them
  • one Slack runs in the desktop session
  • its output is read: a pipe nobody reads is the session's dead output (below)
  • its icon is in a tray (closing the window otherwise leaves it unreachable)
  • a notifier owns org.freedesktop.Notifications
Each finding says what to do

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 from local-settings.json;
  • the logs in ~/.config/Slack/logs/default/. The accounts are counted from the STORE_USER_WORKSPACES entries since the last INITIALIZE;
  • the window tree from xwininfo -root -tree, with the session's DISPLAY. 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

  1. 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, and slack_check names it.
  2. Delete ~/.config/i3/config.d/50-slack.conf. It is the operator's own file, and its rules are now the module's.
  3. 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 and StaleCookies-* 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/slack default, which is the xdg module's.

Relies on

  • i3 (the holder of node-display-session), which places the contribution. The contribution is a dependency on that seat (ADR 0210 §3). Assigning slack where no module holds it is refused.
  • dunst (the holder of node-notifier) for Slack's notifications, which Electron sends to org.freedesktop.Notifications. Nothing in the mesh declares this dependency, because the module makes no contribution to that seat. slack_check reports 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_check reports 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_status answers the tray as unknown.
  • A display server on the same machine (x11-display, ADR 0208 §3).