Skip to main content

Module autostart

Module autostart 

Source
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 bare Run registry 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 calls SMAppService “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. SMAppService is 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 to SMAppService before it is offered to users. (ReAgent P1 on PR #2999.)
  • Linux — XDG autostart .desktop. systemd --user is deliberately NOT the default: surviving logout needs loginctl 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 .desktop filename stem. Stable, because disable() has to find what enable() 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§

STABLE_EXE 🔒

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 true when one was handled and the process should exit.
decode_console_output 🔒
schtasks /XML writes 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, \r escapes. A raw newline would end the line and let a path inject keys, as desktop_exec_escape explains.
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. setting is app:startatlogin, None when never set. target is stable_target(), None when 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. None when there is none.
second_instance_opens_window
A launcher that finds AgentMux already running forwards open_new_window to 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_EXE and 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. APPIMAGE is 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 🔒