Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungRefreshed repository pull requests page generally available(22.09.2026 um 03:25 Uhr)
Sichere ProgrammierungThe Joy of Learning the Basics Again(22.09.2026 um 03:28 Uhr)
Sichere ProgrammierungZero-Code OpenTelemetry Tracing for Dagster(22.09.2026 um 03:39 Uhr)
Linux Tipps & Hardening`prime-all`(22.09.2026 um 02:28 Uhr)
IT Security Toolsopensoho v0.15.2(22.09.2026 um 03:33 Uhr)
IT Security NachrichtenUS Proposes AI Incident Alert System in Talks With China, Bessent Says(22.09.2026 um 04:01 Uhr)
Sichere ProgrammierungRefreshed repository pull requests page generally available(22.09.2026 um 03:25 Uhr)
Sichere ProgrammierungThe Joy of Learning the Basics Again(22.09.2026 um 03:28 Uhr)
Sichere ProgrammierungZero-Code OpenTelemetry Tracing for Dagster(22.09.2026 um 03:39 Uhr)
Linux Tipps & Hardening`prime-all`(22.09.2026 um 02:28 Uhr)
IT Security Toolsopensoho v0.15.2(22.09.2026 um 03:33 Uhr)
IT Security NachrichtenUS Proposes AI Incident Alert System in Talks With China, Bessent Says(22.09.2026 um 04:01 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

5 launchd Traps I Hit Running Claude Code Automation 24/7 on macOS

In earlier posts I walked through a mechanism that auto-generates skills and context auditing. The whole point of these is to run them automatically at a fixed time every night — that's where the value comes from. Right now my setup has 20 …

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

In earlier posts I walked through a mechanism that auto-generates skills and context auditing. The whole point of these is to run them automatically at a fixed time every night — that's where the value comes from. Right now my setup has 20 launchd jobs running (skill generation, context audits, a morning briefing generator, Vault ingestion, and more).



The catch: setting up scheduled jobs on macOS is far trickier than you'd expect. This post covers the 5 traps I actually hit and fixed, along with verified workarounds.






Trap 1: cron is dead on modern macOS



You register a job with crontab -e and it never runs even once. That's the first trap. On modern macOS (Sequoia and later) the cron daemon effectively doesn't run, and jobs are silently skipped. You don't even get an error.



Here's how to check whether it's alive.




log show --predicate 'process == "cron"' --last 7d






If this returns zero entries, cron isn't running. Just migrate to launchd. A launchd job looks like this plist.




<key>Label</key><string>com.you.skill-harvest</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/you/.claude/scripts/skill-harvest.sh</string>
</array>
<key>StandardOutPath</key><string>/Users/you/.claude/logs/skill-harvest.log</string>
<key>StandardErrorPath</key><string>/Users/you/.claude/logs/skill-harvest.log</string>






Loading and running immediately looks like this.




launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.you.skill-harvest.plist
launchctl kickstart gui/$(id -u)/com.you.skill-harvest # force a run to confirm it works







Note: launchctl list / launchctl load are legacy APIs. The modern ones are launchctl print / bootstrap / kickstart. Mixing old and new leads to confusion.







Trap 2: You can't write */5 directly in StartCalendarInterval



Trying to write "every 5 minutes" the way you would in cron gets you stuck. StartCalendarInterval specifies specific times, and periodic syntax like */5 isn't supported.




  • Periodic execution → StartInterval (in seconds; 300 for 5 minutes)

  • Multiple specific times, like "N minutes past every hour" → list several StartCalendarInterval entries in an array



A few smaller traps that also bite:





  • Label must be in com.you.name format. A label without a dot is rejected on load.


  • ProgramArguments must be an array (a bare string is rejected).

  • Unless you specify StandardOutPath / StandardErrorPath as absolute paths, output vanishes into /dev/null.






Trap 3: node: command not found — the minimal-PATH problem of GUI launches



This is where I got stuck the hardest. Claude Code hooks (PostToolUse, etc.) kept failing every time with /bin/sh: node: command not found — even though node works fine in the terminal.



The cause: the /bin/sh -c of a child process spawned by a GUI-launched app doesn't read the login shell's profile (.zshrc); it only has a minimal PATH (/usr/bin:/bin:/usr/sbin:/sbin). So node in nvm or Homebrew isn't visible. launchd jobs fail for the same reason.



The fix is to add env.PATH at the top level of ~/.claude/settings.json and have it inherited by child processes.




"env": {
"PATH": "/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/you/.local/bin"
}






The key is to always include /opt/homebrew/bin. If you hardcode only nvm's version-specific path (.../node/v24.x/bin), it goes stale the moment you do a major Node upgrade and becomes not found again. Keeping /opt/homebrew/bin/node as a fallback means it won't break.



Here's how to reproduce and verify.




# reproduce with the old minimal PATH (not found appears)
env -i HOME="$HOME" /bin/sh -c 'PATH="/usr/bin:/bin"; node --version'
# resolve with the new PATH (the version prints)
env -i HOME="$HOME" /bin/sh -c 'PATH="/opt/homebrew/bin:/usr/bin:/bin"; node --version'









Trap 4: macOS has no timeout



You wrap a script in timeout 60 some-command and get timeout: command not found. macOS doesn't ship the GNU coreutils timeout by default. Many articles and rules are written assuming timeout exists, so copy-pasting them silently misfires.




brew install coreutils   # installs gtimeout






Making your script handle both improves portability.




TIMEOUT_CMD="timeout 60"
command -v gtimeout >/dev/null && TIMEOUT_CMD="gtimeout 60"
$TIMEOUT_CMD some-command






Incidentally, the bash version problem has the same root. The bash bundled with macOS is 3.2, which lacks 5.x features like $EPOCHREALTIME (microsecond-precision time). If you're writing measurement scripts, set the shebang to #!/opt/homebrew/bin/bash to explicitly use 5.x.






Trap 5: The exit 78 (EX_CONFIG) crash loop



The last one was the nastiest. I had a real case where a job with KeepAlive crash-looped infinitely, restarting about 15,000 times over 6 days. No process ever came up, and no logs were left. Looking at launchctl print gui/$(id -u)/<label> showed last exit code = 78: EX_CONFIG, with runs climbing every few seconds.



78 is not the app's own exit — it's launchd's synthesized "couldn't initialize the service," i.e. a failure at the spawn stage. The fact that the log wasn't growing at all (frozen mtime) confirms it. If running the script manually starts up normally, the program itself is healthy and it's dying only under launchd.



Diffing against a healthy job's plist, I found three causes that mattered.





  1. The log path is in a TCC-protected area (~/Documents, ~/Desktop, etc.) → change it to ~/.claude/logs/ or ~/Library/Logs/ (this was the most likely culprit)


  2. The shebang #!/usr/bin/env bash resolves, under the plist's PATH, to an unsigned Homebrew bash → set ProgramArguments to ["/bin/bash", "script.sh"] to explicitly use the Apple-signed interpreter


  3. WorkingDirectory unspecified → launchd's default cwd is /. If the app writes data with relative paths, it writes to an unintended location (like /tmp, which is wiped on reboot) and loses the data



Order matters in the repair procedure.




# 1. stop the loop
launchctl bootout gui/$(id -u)/<label>
# 2. kill the orphan process holding the port, "after confirming its owner"
lsof -nP -iTCP:<port> -sTCP:LISTEN
# 3. fix the plist and reload it
launchctl bootstrap gui/$(id -u) <plist>
# 4. verify
launchctl print gui/$(id -u)/<label> | grep -E 'state =|runs =|last exit'







Warning: KeepAlive=true does not fix a spawn failure. Without a ThrottleInterval (around 30 seconds), an infinite loop at intervals of a few seconds piles runs up into the tens of thousands.




One last landmine. When inspecting a plist, if you get the arguments to plutil -extract ... -o <file> wrong, you overwrite and destroy the original plist with the output (one of my job's plists actually became a 76-byte JSON fragment). When investigating with plutil, always output to -o - (stdout) or a separate file.






Summary
































Trap Workaround
cron doesn't run Check liveness with log show → migrate to launchd
Can't write */5

StartInterval (seconds) or expand into an array
node: command not found Add /opt/homebrew/bin to env.PATH in settings.json
No timeout

brew install coreutilsgtimeout fallback

exit 78 loop
Log path outside TCC / explicit /bin/bash / WorkingDirectory / ThrottleInterval


Automation isn't "set it and forget it" — it's only complete once liveness checking is part of it. If you build the habit of periodically checking with launchctl print whether the log's mtime is advancing as expected, you'll notice jobs that have silently died much sooner.



Across these three posts, I've built an environment where skills grow, context stays light, and automation runs around the clock. Next, I plan to write about the mechanism that rolls all of this up into a morning briefing.






Written by **Lily* — I ship iOS apps and automate my content stack with Claude Code.

Follow along: Portfolio · X · GitHub*

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten 5 launchd Traps I Hit Running Claude Code Automation 24/7 on macOS

Thematisch verwandte Begriffe: launchd, Traps, Running, Claude · 6 Treffer

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-49449 | Joplin is an open source note-taking and to-do application that organise…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick