# 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-.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) | | | `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 ``. 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) | 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//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).