Files
hq/01-RESEARCH/025-how-a-module-plugs-into-the-shell/01-what-the-shell-file-holds-today.md
T
jochen ac6c306df3 Research 025: how a module plugs into the operator's shell
Opened after the zsh module's rollout (to-be 38 WP5) was stopped: every machine
carries the same predecessor-written startup file, the module's block would
duplicate it and drop lines, nothing installs the prompt, and execute never
reads .zshrc. Weighs how modules contribute environment and shell code, where
the operator's own lines go, ordering, and what a contribution is addressed to.
2026-10-04 10:30:18 +02:00

6.4 KiB

01 — What the shell file holds today

Measured 2026-10-04 on the four machines of one installation: two servers and two workstations. All four have the account's login shell set to zsh, zsh installed from the distribution, and a predecessor-written startup file. The predecessor is retired, so nothing manages these files any more.

The startup file is the same everywhere

The account's ~/.zshrc is byte-identical on all four machines: 102 lines, one checksum. ~/.zshrc.local, which the last line of ~/.zshrc sources, comes in two variants: one shared by both servers, and one shared by both workstations. So the "per-machine" part is really a per-kind-of-machine part.

The predecessor produced these from one module with two flavors: a prompt flavor and an autocomplete flavor, each of which swapped in a different local file. Its install hook also:

  • cloned the prompt theme and three plugins from their upstream repositories into ~/.zsh/;
  • installed fonts;
  • changed the login shell.

On the workstations the theme and plugins are still on disk, left over and now owned by nothing. The servers have none of them.

What the 102 lines are

Sorted by who should own each line once the machine is modules:

Lines today What they are Natural owner
EDITOR, VISUAL, XDG_CONFIG_HOME, PATH gaining ~/.local/bin and two script directories the account's environment the shell's default, or the environment itself
PATH gaining a toolchain's directory environment, for one tool the toolchain's module
a version manager's directory variable plus sourcing its loader environment and shell code the version manager's module
two variables naming the operator's own script library environment, the operator's own the operator
a variable that turns off an agent's terminal-title handling environment, for one tool the agent's module
the terminal title hook, keybindings, dircolors, the ls/grep aliases, ll/la/l, a container-run alias, two disk-usage functions, two port aliases interactive shell behaviour the shell's default
the prompt's instant-prompt cache, the theme, the prompt's own configuration file shell code, order-sensitive (instant prompt first) the prompt module
autosuggestions, syntax highlighting (and, unloaded, an autocomplete plugin on disk) shell code, order-sensitive (syntax highlighting last) a plugin module, or the prompt module
sourcing ~/.zshrc.local the operator's hook the operator

The workstation variant of the local file adds:

  • more environment: a desktop toolkit theme, a file manager's plugin list, BROWSER, VISUAL overridden to a graphical editor, a language toolchain's binary directory on PATH;
  • two pieces of shell code: one that pads the prompt to the bottom of the terminal under a display, and one that sources a function file another module places;
  • a hook sourcing a further per-node file.

The server variant holds only that last module-placed source line.

Count: a workstation runs 65 non-comment lines from the two files (53 shared, 12 local); a server runs 54. Of a workstation's 65:

  • about a quarter (15) are environment;
  • about half are interactive defaults no other module cares about;
  • the remaining quarter is other modules' code and hooks (a prompt, plugins, a version manager, an agent's functions), loaded from the shell file only because there was nowhere else to put it.

The shell module as written

The zsh module of to-be 38 WP5 (catalogue change, unmerged):

  • writes one block, appended at the end of ~/.zshrc, holding a subset of the shared file:
    • its environment lines, minus the toolchain directory, the version manager and the agent variable;
    • the title hook, keybindings and the most common aliases, minus the port aliases;
    • guarded source lines for the theme and two plugins if present;
    • the source of ~/.zshrc.local.
  • assigned to any of the four machines, appends that block after the identical lines already there, so every line in it runs twice, ~/.zshrc.local included.
  • on the servers, the guarded prompt lines find nothing; nothing installs the theme anywhere.

Which startup file reaches what

zsh's startup order, and what each path through it reads:

started as reads
interactive login (a console, ssh with a terminal) .zshenv, .zprofile, .zshrc, .zlogin
interactive non-login (a new terminal window) .zshenv, .zshrc
non-interactive login: zsh -lc …, what the execute verb runs .zshenv, .zprofile, .zlogin, not .zshrc
non-interactive: a script, ssh host command .zshenv only

So an environment written into .zshrc reaches neither execute nor a script. The distribution's system-wide login profile, which zsh's system zprofile sources, only ever appends to PATH when an entry is missing. An entry the account's .zshenv puts first therefore survives a login.

A graphical session's programs (a launcher, a bar, a window manager's key bindings) are started from the display manager and the service manager, not from a shell, and read none of these files. The service manager's own place for the account's environment is ~/.config/environment.d/. Today it holds nothing on any of the four machines, so a program launched from the window manager does not see PATH entries that a terminal does.

What the mesh already has for "many modules, one file"

Measured over the catalogue's 69 module definitions:

mechanism used by shape
contributes / receives 28 contribute, 15 receive A consumer contributes facts keyed by a requirement. The provider receives all of them as one file in the mesh's own format, and renders them itself. "The controller does not know what a reverse proxy is."
listens / filtering 40 declare listens, 1 composes The controller derives the whole firewall rule set from every module's ports and writes it where the holder asks.
jails / jailing 3 declare, 1 composes Each module supplies its jail in the tool's own format. The controller assembles them, sorted, into the one file the holder names.
into: block on a file 2 One module's marked region inside a file something else owns. Text outside the region is kept byte for byte. Placement is at the end, or at the start.

None of these is a contribution of shell code or of environment today.