DEV Community

Cover image for I finally figured out why my Mac mini kept killing agents overnight when the screen was off
Lars Winstand
Lars Winstand

Posted on Originally published at standardcompute.com

I finally figured out why my Mac mini kept killing agents overnight when the screen was off

I lost an embarrassing amount of time blaming the wrong layer.

At 6 p.m., my local agent stack looked fine.

The Mac mini was watching a repo, pushing jobs into n8n, fanning work out to downstream workers, and occasionally calling GPT-5 for planning and Claude Opus 4.6 for cleanup. Everything looked healthy.

By morning, the logs were dead.

No obvious crash. No stack trace. Just silence after around 1 a.m., like macOS had quietly decided my automations were no longer invited.

I blamed the models first.
Then n8n.
Then Redis.
Then Watchman.
Then my own code.

All of those can fail.

But on a headless Mac mini, the first thing to suspect is usually much more boring:

macOS power state and launchd setup.

If you need to keep a Mac mini awake headless, this is the stuff that actually matters.

The symptom that fooled me

A headless Mac mini can still respond on the network and look vaguely alive even when the worker you care about is gone.

That’s what makes this annoying.

You can still SSH in sometimes. Ping works. The machine exists. But your agent, watcher, or queue worker has stopped doing useful work.

The key detail is that on macOS, display sleep and system sleep are not the same thing.

If you only think in terms of “the screen turned off,” you can debug the wrong problem for days.

Display sleep vs system sleep on macOS

These are separate settings:

  • displaysleep controls when the display sleeps
  • sleep controls idle system sleep

So yes, your Mac mini can have the display off and still be fine.

But it can also enter idle sleep and take your automation stack down with it.

Start by checking the actual power config instead of guessing.

pmset -g
pmset -g assertions
Enter fullscreen mode Exit fullscreen mode

What they tell you:

  • pmset -g shows configured power settings
  • pmset -g assertions shows what is currently preventing sleep

That second command is the one that exposes a lot of fake “server” setups.

The SSH trap

I hit this one too.

You SSH into the Mac mini, run your workers, and everything stays alive. So you assume the box is configured correctly.

Not necessarily.

macOS has ttyskeepawake, which can help prevent idle sleep while a tty session is active.

That means your SSH session may be the only thing keeping the machine awake.

Once the session drops, the machine can go right back to sleeping unless:

  • idle sleep is disabled
  • another process is holding a sleep-preventing assertion
  • you configured the system correctly for headless use

That’s why these failures feel random.

They’re usually not random.

Your terminal session was acting like life support.

The launchd mistake that breaks "always-on" workers

The next problem is launchd.

This one is sneaky because it often works perfectly during the day.

A lot of people start with a LaunchAgent because it’s easy to test from a logged-in user session.

That’s fine for user-scoped automation.

It’s a bad fit for infrastructure-y workers that need to survive reboots, logouts, and no attached display.

Here’s the practical difference:

Option What it really means
LaunchAgent Per-user job. Runs in a user session. Can disappear when the user logs out or when the GUI session changes
LaunchDaemon System-level job. Better fit for always-on background workers not tied to an interactive login

If your agent runner only works while you’re logged into Finder or Terminal, it is not actually always-on.

It’s a desktop app in denial.

When to use LaunchDaemon instead

If the process needs to:

  • survive reboots
  • run with no monitor attached
  • keep processing jobs after logout
  • behave like a service instead of a personal script

then it probably wants to be a LaunchDaemon.

Not everything belongs there.

But if you’re running queue workers, repo indexers, bot processes, or OpenClaw-style automations 24/7, tying them to a user session is asking for overnight failures.

The other failure mode: the process is alive, but the workflow is dead

This is where file watchers make things confusing.

A lot of local agent stacks depend on file changes:

  1. repo changes happen
  2. Watchman sees them
  3. indexing or build logic runs
  4. n8n picks up downstream work
  5. model calls happen after that

When the chain breaks, people often blame the LLM call because that’s the visible step.

But the failure can happen much earlier.

On macOS, Watchman can run into FSEvents issues after sleep/wake or event backlog. When that happens, you can get recrawls, dropped events, or delayed triggers.

That can look exactly like "my agent got flaky overnight."

What actually happened is your watcher got wrecked.

Watchman setting worth testing

If you depend on Watchman on macOS, this is worth trying:

{
  "fsevents_latency": 0.5
}
Enter fullscreen mode Exit fullscreen mode

Put that in .watchmanconfig, then restart Watchman:

watchman shutdown-server
Enter fullscreen mode Exit fullscreen mode

Things to watch for:

  • recrawl warnings
  • delayed file triggers
  • bursts of false-positive changed files after wake
  • downstream jobs that look stuck but are really waiting on the watcher layer

If you’re seeing recrawl warnings constantly, don’t just suppress them and move on. Understand why they’re happening first.

What "screen off killed my agents" usually really means

In practice, that sentence usually maps to one of four root causes:

  1. The Mac entered idle system sleep, not just display sleep
  2. The worker was started as a LaunchAgent, so it depended on the user session
  3. An SSH session was the only thing keeping the machine awake
  4. Watchman or another file watcher fell behind after sleep/wake and broke downstream automation

Different root causes, same symptom:

"Everything worked yesterday and now the pipeline is dead."

My baseline fix for a headless Mac mini

This is the first setup I wish I had done on day one.

sudo pmset -c sleep 0
sudo pmset -c displaysleep 10
sudo pmset -c ttyskeepawake 1
Enter fullscreen mode Exit fullscreen mode

What those do:

  • sleep 0 disables idle system sleep while on charger power
  • displaysleep 10 still allows display sleep separately
  • ttyskeepawake 1 lets active tty sessions like SSH help prevent sleep

Then verify the result:

pmset -g
pmset -g assertions
Enter fullscreen mode Exit fullscreen mode

And don’t stop there.

Do these checks too:

  • move critical workers from LaunchAgent to LaunchDaemon if they truly need to be always-on
  • send logs somewhere readable without a GUI session
  • test after a full reboot with no interactive login
  • test overnight without leaving an SSH session open
  • if you use Watchman, inspect recrawls and FSEvents warnings after sleep/wake

That last one matters.

If the setup only works while Terminal is open, it’s not a server.

It’s a supervised demo.

Why n8n and agent workflows expose this faster

n8n makes these problems obvious because reliability is the product.

If you’re just tinkering during the day, a single local instance can feel fine.

But once you start doing real automation work, the stack grows fast:

  • n8n
  • Redis
  • workers
  • file watchers
  • scheduled jobs
  • repo sync
  • multiple model calls
  • retries and queueing

At that point, OS behavior becomes more important than model quality.

That’s the part people underestimate.

Developers love arguing about GPT-5 vs Claude Opus 4.6 vs Grok 4.20.

Sure, model choice matters.

But if your Mac mini sleeps, drops the user session, or breaks the watcher chain overnight, none of that matters.

Your benchmark winner is now doing zero work.

The real lesson: fix the machine before you blame the model

The most annoying part of self-hosted agent setups is that the hard part often isn’t AI.

It’s the OS.

It’s whether your “server” is actually:

  • a sleeping desktop
  • running a per-user background job
  • depending on an active SSH session
  • using a watcher that falls apart after wake

That’s much less exciting than model evals.

It’s also the difference between an agent that runs for a week and one that quietly dies every night.

Practical checklist

If your Mac mini is running automations, agents, or local workers, this is the checklist I’d use:

# Inspect current power settings
pmset -g

# Inspect active sleep-prevention assertions
pmset -g assertions

# Disable idle system sleep on charger power
sudo pmset -c sleep 0

# Keep display sleep separate
sudo pmset -c displaysleep 10

# Let active SSH/tty sessions help keep the system awake
sudo pmset -c ttyskeepawake 1
Enter fullscreen mode Exit fullscreen mode

Then verify architecture, not just commands:

  • critical worker uses LaunchDaemon, not LaunchAgent
  • logs are available without GUI login
  • reboot test passes
  • overnight test passes with no SSH session
  • watcher layer is healthy

One more thing if you’re scaling agent workflows

If you’re building automations that call multiple models all day, the Mac mini problem is really a smaller version of a bigger ops problem.

Once you have background workers constantly routing jobs between tools, the expensive part is no longer just model quality. It’s reliability and cost predictability.

That’s why teams eventually move away from babysitting token spend and start caring more about stable, always-on execution. If your workflows are hammering GPT-5, Claude, and Grok through automations, predictable infrastructure matters as much as prompts do.

That’s also why services like Standard Compute exist: same OpenAI-compatible API shape, but built for agent-heavy workloads that run all the time without per-token panic. If your automations are finally stable, the next bottleneck is usually cost.

Final takeaway

If your headless Mac mini keeps "randomly" killing agents overnight, start here before touching prompts, model routing, or workflow logic:

  1. inspect pmset
  2. inspect sleep assertions
  3. stop depending on SSH as your wake strategy
  4. move always-on workers to LaunchDaemon
  5. check Watchman and file-watcher health

My actual fix was boring.

And that was the whole point.

Fix the OS before you blame the LLM.

Top comments (0)