All posts
Projects · May 2026 · 4 min read

Staggered - delay app launches at macOS login

macOS gives you one option for login items — they all fire at once. I wanted some apps to wait. So I built a small SwiftUI app that staggers launches with configurable delays.

Oliver Bagley

Oliver Bagley

eCommerce Manager & Digital Systems

Staggered - delay app launches at macOS login

macOS login items are all-or-nothing. Every app you’ve added fires at the same moment — which means they all compete for CPU and disk at the worst possible time. Slower apps fail to open properly, others show up with missing state, and your machine takes longer to settle than it should.

The obvious fix is to spread the launches out. Turns out there’s no built-in way to do that.

What Staggered does

You add apps to a list, set a delay in seconds for each one, and toggle Run at Login. That’s it. From that point, every time you log in, Staggered wakes up silently in the background, waits the right amount of time for each app, opens them in order, then quits without ever showing a window or a dock icon.

The GUI is only there when you open it yourself — from Finder, Dock, or Spotlight. It knows the difference based on whether it was launched with a --login argument, which the LaunchAgent plist passes explicitly. There’s no guesswork involved.

Two timing modes

Sequence mode stacks delays cumulatively. If you set Slack to 5s and Spotify to 8s, Slack launches at t+5s and Spotify at t+13s (5 + 8). Each app waits for the previous gap to close.

Parallel mode runs all timers simultaneously from boot. The same settings would give you Slack at t+5s and Spotify at t+8s, independently. Neither waits for the other.

Parallel is useful when your apps don’t depend on each other and you just want to spread the load. Sequence is better when launch order actually matters — for example if one app needs to be running before another connects to it.

How the login detection works

The bit I spent the most time getting right was reliably telling the difference between a login launch and a user launch. A few approaches turned out to be wrong.

Checking the parent process ID seemed like a good idea — login items launched by launchd should have PID 1 as their parent. Except on modern macOS, apps opened from the Dock are also ultimately parented to launchd, so that check fires for both cases.

SMAppService.mainApp is the modern API for login item registration, but it doesn’t support passing custom arguments. So you can register the app, but you can’t tell it anything about why it’s launching.

The solution was to skip SMAppService entirely and write a LaunchAgent plist directly to ~/Library/LaunchAgents/. This gives full control over ProgramArguments, so the plist explicitly passes --login. When the app sees that flag it runs headless; when it doesn’t, it opens the configurator. Simple and unambiguous.

Building it

Staggered is built with SwiftUI and AppKit and has no external dependencies. The build is a single build.sh script using swiftc directly — no Xcode project file needed.

chmod +x build.sh
./build.sh
cp -r "build/Staggered.app" ~/Applications/
open ~/Applications/Staggered.app

Move it to a permanent location before enabling Run at Login, since the LaunchAgent plist stores an absolute path to the executable. If you move it afterwards, just toggle the login item off and back on to rewrite the plist.

Logs from login launches go to /tmp/staggered.log if anything needs debugging.

If this app seems useful to you, then the code is available on GitHub - Staggered App.

Work together

Got something to
work on?

If something here resonated, I'd like to hear from you.

More posts

Projects · March 2026 · 4 min read

SonoCast - streaming vinyl to Sonos via Icecast

I wanted to play vinyl through my Sonos. Sonos doesn't accept line-in from a USB sound card. So I built a two-container Docker stack that grabs the audio, encodes it, and streams it out as a custom radio station.

Projects · March 2026 · 3 min read

WipeMode - lock your keyboard while you clean it

I was cleaning my keyboard one day and sent a dozen emails in the process. So I built a small macOS app that locks all input while you wipe down. One click to lock, hold a shortcut to unlock.