Skip to content
cccskills
FreePublished on GitHub

macos-launch-at-login

Use when adding, reviewing, or debugging Launch at Login in a macOS app — SMAppService registration, the requiresApproval state, a Settings toggle that reflects reality, and migration off deprecated login-item APIs.

The English summary is being prepared. Showing the original description.

Run score Not tested

See how to install

Files included (1)

  • SKILL.md7.4 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

macOS Launch at Login

Add Launch at Login with SMAppService (macOS 13+), expose it as an honest Settings toggle, and handle every status the system can return — including the one where the user said no.

When to use

Use this skill when the user says:

  • launch at login
  • start at login / start on boot
  • open at startup
  • login item
  • SMAppService
  • SMLoginItemSetEnabled (migration)
  • background helper that should always run
  • menu bar app that should come back after restart

Do not use this skill for launch daemons or privileged helpers (SMAppService.daemon + launchd plists are a different, root-level topic), or for iOS.

Core rule

Launch at Login is opt-in, visible in Settings, honest about its
current state, and trivial to turn off. The app never registers
itself without the user asking.

The API

One import, one service, three calls:

import ServiceManagement

// Register (adds the app to System Settings > General > Login Items)
try SMAppService.mainApp.register()

// Unregister
try SMAppService.mainApp.unregister()

// Current truth — always read this, never cache your own flag
SMAppService.mainApp.status

register() takes effect immediately and persists across reboots. No helper bundle, no Library/LaunchAgents plist, no deprecated API.

The four statuses

SMAppService.Status is the part most implementations get wrong. Handle all four:

StatusMeaningWhat your UI shows
.enabledRegistered and allowedToggle on
.notRegisteredNot a login itemToggle off
.requiresApprovalRegistered, but the user disabled it in System SettingsToggle off + a "Needs approval in System Settings" hint with a button
.notFoundSystem can't find the registration (rare; moved/renamed app)Toggle off; re-register on next enable

The killer detail: the user can flip your login item in System Settings > General > Login Items at any time, behind your back. A cached boolean in UserDefaults will lie. Read status every time the Settings view appears and every time the app becomes active.

For .requiresApproval, send the user to the right pane:

SMAppService.openSystemSettingsLoginItems()

A complete, honest toggle

import SwiftUI
import ServiceManagement

@MainActor
@Observable
final class LaunchAtLogin {
    private(set) var status: SMAppService.Status = .notRegistered

    var isEnabled: Bool { status == .enabled }
    var needsApproval: Bool { status == .requiresApproval }

    init() { refresh() }

    func refresh() {
        status = SMAppService.mainApp.status
    }

    func setEnabled(_ enabled: Bool) {
        do {
            if enabled {
                try SMAppService.mainApp.register()
            } else {
                try SMAppService.mainApp.unregister()
            }
        } catch {
            // Registration can fail (e.g. translocated app run from a DMG).
            // Fall through to refresh() so the toggle snaps back to the truth.
        }
        refresh()
    }
}

struct LaunchAtLoginToggle: View {
    @State private var model = LaunchAtLogin()

    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Toggle("Launch at Login", isOn: Binding(
                get: { model.isEnabled },
                set: { model.setEnabled($0) }
            ))

            if model.needsApproval {
                HStack(spacing: 6) {
                    Text("Waiting for approval in System Settings.")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                    Button("Open Login Items…") {
                        SMAppService.openSystemSettingsLoginItems()
                    }
                    .buttonStyle(.link)
                    .font(.caption)
                }
            }
        }
        // The user can change this in System Settings while we're not looking.
        .onReceive(NotificationCenter.default.publisher(
            for: NSApplication.didBecomeActiveNotification)) { _ in
            model.refresh()
        }
    }
}

Notes that keep this correct:

  • The setter never stores the requested value. It attempts the change, then re-reads status. If registration failed or needs approval, the toggle reflects that instead of lying.
  • Refresh on didBecomeActiveNotification so a trip to System Settings and back updates the UI.
  • Errors are non-fatal. A failed register() on a translocated build (running straight from a DMG) is expected; the fix is "move the app to /Applications", not a crash.

Start hidden

If the app launches at login, it should not shove a window in the user's face at boot.

  • Menu bar apps: set LSUIElement = YES and don't open a window on launch — nothing else needed. This is the common case (see macos-menubar-swiftui).
  • Regular windowed apps: offer a separate "Start hidden" toggle and skip restoring the main window when it's on. Keep it a separate toggle — bundling it into Launch at Login removes the user's choice.

Migrating off the deprecated APIs

If the codebase still uses any of these, replace them with SMAppService.mainApp:

  • SMLoginItemSetEnabled + a separate login-item helper bundle in Contents/Library/LoginItems — delete the helper target entirely.
  • LSSharedFileList (ancient, removed).
  • A hand-written ~/Library/LaunchAgents/*.plist for a GUI app — user-visible, fragile, and now shown with a scary "background item added" notification. Login items belong to SMAppService.

There is no data to migrate: on first run of the new version, read SMAppService.mainApp.status and treat it as the truth. Don't try to auto-re-register based on the old preference — that's registering without the user asking.

Settings UX

General
  [ ] Launch at Login
      Waiting for approval in System Settings.  Open Login Items…   (only when required)
  [ ] Start hidden                                                  (windowed apps only)
  • The toggle lives in Settings (or the menu bar app's preferences), not in a first-run modal.
  • Never pre-check it, never register during onboarding "for convenience".
  • The approval hint appears only in the .requiresApproval state.

Checklist

[ ] Uses SMAppService.mainApp — no helper bundle, no SMLoginItemSetEnabled
[ ] Toggle reads status from the system, not from a cached UserDefaults flag
[ ] All four statuses handled, including .requiresApproval with a
    "Open Login Items…" path
[ ] Status refreshes when the app becomes active
[ ] register()/unregister() errors leave the toggle showing the real state
[ ] App never registers itself without an explicit user action
[ ] Launch-at-login start is quiet (no window thrown at the user at boot)
[ ] Turning it off works and survives a relaunch

Manual test

  1. Toggle on → app appears in System Settings > General > Login Items.
  2. Reboot (or log out/in) → app starts, quietly.
  3. Disable it in System Settings → return to the app → toggle shows off (or "needs approval").
  4. Toggle off in-app → entry disappears from Login Items.
  5. Run the app from a mounted DMG → toggle fails gracefully, no crash.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Use when creating a new project folder, starter pack, app scaffold, or agent-ready workspace — AGENTS.md, README, TASKS, DECISIONS, PROMPTS, an idempotent bootstrap script, and starter-pack modes.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when designing or implementing states for AI agent tasks, background jobs, local model runs, queues, and notifications — one shared set of states, only legal transitions, and a recovery path out of every failure.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when exposing app features to App Intents, Shortcuts, Siri, Spotlight, widgets, controls, or system actions — small safe AppIntents, AppShortcutsProvider, AppEnum/AppEntity, parameters, and open-vs-background behavior.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when designing, reviewing, or implementing a command-palette / ⌘K layer for an Apple-platform app — keyboard-first global search, ranked and grouped results, native floating panel, and protected destructive commands.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when designing or implementing empty, first-run, no-result, offline, error, or waiting states in Apple-platform apps — every empty state explains what is missing, why it matters, and what to do next.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

Use when designing or implementing feedback, status, progress, alerts, errors, success states, and live activity indicators in Apple-platform apps — inline status, banners, alerts, notifications, and loading states.

The English summary is being prepared. Showing the original description.

aka-kika/akakika-skills11Updated Sep 12, 2026

See all skills by aka-kika

Report a problem with this skill