Expand description
Auto-start-at-login registration — issue #2977 Workstream 2,
docs/specs/SPEC_TRAY_OPTIONAL_BACKGROUND_SERVICE_2026_09_04.md §4.
§Opt-in, off by default, and separate from “close to tray”
Nothing here runs unless the user explicitly asks for it. The issue is
emphatic that these are two different decisions: “a user can have
‘close-to-tray keeps it running for this session’ without ‘starts
automatically at every login’ being bundled into the same toggle”. So
auto-start has its own state, its own enable/disable, and is never
implied by AGENTMUX_TRAY or AGENTMUX_BACKGROUND_SERVICE.
§Mechanism per platform, and what was rejected
- Windows — Scheduled Task,
LogonType=InteractiveToken. Not a Windows Service (needs admin to register, and Session-0 isolation means it structurally cannot show a tray icon), and not a bareRunregistry key (no crash supervision, no delay control). - macOS — LaunchAgent plist. This DEVIATES from the governing spec, and
the deviation has a user-visible consequence.
SPEC_TRAY_OPTIONAL_BACKGROUND_SERVICE_2026_09_04.md§4 callsSMAppService“unambiguously current best practice” and notes it is the only path that surfaces correctly in System Settings → Login Items & Extensions. Writing the LaunchAgent directly means the entry may not appear there the way a user expects, which matters more than usual here: Workstream 4 requires that a user be able to see and remove a background service, and Login Items is where they will look.SMAppServiceis only callable through its ObjC interface from inside a bundled, signed app, so adopting it means objc2 bindings plus a bundle identifier that matches the registered agent — real work that cannot be written blind or verified on a non-macOS machine. Recorded as a known gap rather than pretended away: macOS auto-start should move toSMAppServicebefore it is offered to users. (ReAgent P1 on PR #2999.) - Linux — XDG autostart
.desktop.systemd --useris deliberately NOT the default: surviving logout needsloginctl enable-linger, which is a separate, consequential decision the issue says must never be silently enabled.
§Uninstall is a first-class operation (Workstream 4)
WS4 requires “one complete, independently-testable uninstall path that
actually removes the LaunchAgent/Scheduled-Task/autostart artifact — not
just closes the window”. disable() is that path, and is_enabled()
makes it independently checkable: the pair is the whole contract, and
disable() is idempotent so an uninstaller can call it unconditionally.
§Structure
The artifact content (Task Scheduler XML, plist, desktop entry) is
generated by pure functions that take an exe path and return a string.
That keeps the part most likely to be subtly wrong — escaping, schema
fields, the InteractiveToken logon type — unit-testable without
touching the machine’s real login configuration.
Structs§
- Entry
- The login entry as it stands on this machine.
Enums§
- Plan
- What to do to bring the login entry in line with
app:startatlogin(SPEC_START_WITH_OS_2026_09_25.md§3.9).
Constants§
- AUTOSTART_
ID - Identifier used for the artifact on every platform: the Scheduled Task
name, the LaunchAgent label’s suffix, and the
.desktopfilename stem. Stable, becausedisable()has to find whatenable()created — including across upgrades, so it deliberately carries no version. - BACKGROUND_
FLAG - The flag every generated artifact passes to the launcher.
- MACOS_
LABEL - Reverse-DNS label for the macOS LaunchAgent, which requires that form.
- PLIST_
CHANNEL_ 🔒KEY - Owning channel, as a plist key launchd ignores.
- STABLE_
EXE_ 🔒ENV - Env var the Linux AppRun and wrappers set to their own path right before they exec the launcher.
- TASK_
CHANNEL_ 🔒MARKER - Marks the owning channel inside the Task’s
<Description>, the only free-text field Task Scheduler keeps verbatim. - XDG_
CHANNEL_ 🔒KEY - Owning channel, as a vendor key (
X-keys are ignored by the session).
Statics§
Functions§
- artifact_
path - Where the artifact lives, for the platforms that use a plain file. Not used on Windows, whose artifact lives in the Task Scheduler store rather than a path we own.
- background_
env_ for - Environment the host must receive when the launcher was started with
--background. - background_
requested - Did the launcher get started in background mode?
- between 🔒
- current_
channel - Handle the auto-start CLI verbs, if present. Returns
truewhen one was handled and the process should exit. - decode_
console_ 🔒output schtasks /XMLwrites UTF-16LE when its output is redirected.- desktop_
exec_ 🔒escape - Escape a path for use inside a quoted argument of a desktop entry’s
Exec=value. - desktop_
string_ 🔒escape - Escape a plain desktop-entry string value (
TryExec, vendor keys), per the Desktop Entry spec’s\\,\n,\t,\rescapes. A raw newline would end the line and let a path inject keys, asdesktop_exec_escapeexplains. - desktop_
string_ 🔒unescape - disable
- Remove auto-start registration. Idempotent — succeeds when nothing was registered, so an uninstaller can call it unconditionally without first asking whether the feature was ever turned on.
- enable
- Register auto-start for
exe. Idempotent: re-registering an existing entry replaces it, which is also how an upgrade repoints the artifact at a new install path. - handle_
cli - is_
enabled - Is auto-start currently registered?
- launch_
agent_ plist - macOS LaunchAgent plist.
- login_
start - Was this process started by a login entry? Every artifact passes
--background, and nothing else does. - parse_
desktop_ 🔒entry - parse_
plist 🔒 - parse_
task_ 🔒xml - plan
- Pure decision.
settingisapp:startatlogin,Nonewhen never set.targetisstable_target(),Nonewhen it could not be resolved: only writing needs it, so removing or adopting never waits on it (ReAgent P1 on #3788). - read_
entry - Read the current entry back from the platform.
Nonewhen there is none. - second_
instance_ opens_ window - A launcher that finds AgentMux already running forwards
open_new_windowto it, so a double-click opens a window. A login entry firing while the user already has AgentMux open must not (SPEC_START_WITH_OS_2026_09_25.md§3.6): it just exits. - stable_
target - What the login entry should run: something that survives updates
(
SPEC_START_WITH_OS_2026_09_25.md§3.1), not this process’s own path. - stable_
target_ 🔒from - take_
stable_ exe_ env - Read
AGENTMUX_STABLE_EXEand remove it from this process’s env, so no child (host, srv, agent shells, a dev build started from a pane) inherits a path that belongs to this launch.APPIMAGEis not used for the same reason: the AppImage runtime’s copy leaks into every agent shell. - xdg_
desktop_ entry - Linux XDG autostart desktop entry.
- xml_
escape 🔒 - Minimal XML text escaping for the two generated XML documents.
- xml_
unescape 🔒