Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Macos Launchd Supervision

BSecurity

Author correct macOS launchd plists for long-lived local daemons and design the EXTERNAL supervision-integrity check that catches what KeepAlive cannot self-heal: the supervising job itself getting unloaded. Use when writing or reviewing a LaunchAgent/LaunchDaemon plist, debugging a daemon that "silently died" after a brew upgrade or logout, choosing LaunchAgent vs LaunchDaemon, or fixing launchd's minimal-PATH "command not found" failures. NOT for systemd/Linux unit files, Windows services, ...

2 stars
0 votes
0 copies
1 views
Added 9/24/2026
toolsrustgoshellbashnodedockerdebugginggitdevopssecurity

Works with

terminal

Security Analysis

B75/100
criticalModifies startup scripts or system services for persistence

Pro scans all 11 files and shows the line behind each finding

Scanned 10/2/2026

$npx -y skills add curiositech/port-daddy --skill macos-launchd-supervision --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Macos Launchd Supervision?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Macos Launchd Supervision
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/curiositech-macos-launchd-supervision/badge)](https://www.skillsdirectory.com/skills/curiositech-macos-launchd-supervision)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
Files
SKILL.md
---
name: macos-launchd-supervision
description: >-
  Author correct macOS launchd plists for long-lived local daemons and design the EXTERNAL
  supervision-integrity check that catches what KeepAlive cannot self-heal: the supervising job
  itself getting unloaded. Use when writing or reviewing a LaunchAgent/LaunchDaemon plist,
  debugging a daemon that "silently died" after a brew upgrade or logout, choosing LaunchAgent vs
  LaunchDaemon, or fixing launchd's minimal-PATH "command not found" failures. NOT for
  systemd/Linux unit files, Windows services, Docker healthchecks, or the daemon's own business
  logic (see daemon-development).
license: Apache-2.0
allowed-tools: Read,Write,Edit,Bash,Grep,Glob
metadata:
  category: Infrastructure & DevOps
  tags:
    - launchd
    - macos
    - plist
    - daemon-supervision
    - keepalive
  provenance:
    kind: first-party
    owners:
      - port-daddy
  pairs-with:
    - skill: daemon-development
      reason: Supplies the daemon's own lifecycle, graceful shutdown, and IPC once launchd supervises it correctly.
    - skill: macos-host-security
      reason: Supplies entitlements, code signing, and sandboxing concerns adjacent to a launchd job.
    - skill: port-daddy-internal-dev
      reason: Home of the real `pd doctor supervision-integrity` precedent (PR #607) this skill generalizes.
  io-contract:
    kind: deliverable
    consumes:
      - kind: launchd-job-plan
        format: json
    produces:
      - kind: plist-lint-report
        format: json
      - kind: annotated-plist
        format: markdown
---

# macOS Launchd Supervision

Author launchd jobs that actually stay supervised, and build the external check that notices when they don't.

## Use This For

- Writing a LaunchAgent or LaunchDaemon plist for a long-lived local daemon (Label, ProgramArguments, RunAtLoad, KeepAlive, ThrottleInterval, log paths).
- Deciding LaunchAgent (`~/Library/LaunchAgents`, per-user GUI session) vs LaunchDaemon (`/Library/LaunchDaemons`, system-wide, no user session).
- Debugging "the daemon silently died and nothing restarted it" — almost always a supervisor that got unloaded, not a KeepAlive bug.
- Designing an EXTERNAL supervision-integrity check, modeled on `pd doctor`, that verifies the *supervisor* is loaded — something KeepAlive cannot verify about itself.
- Fixing launchd's minimal-PATH environment causing `command not found` for tools that work fine in a terminal.

## Do Not Use This For

- systemd unit files, Windows service registration, or any non-Darwin service manager — different lifecycle model entirely.
- Docker/container healthchecks and restart policies.
- The daemon's own business logic, protocol, or graceful-shutdown handling — see `daemon-development`.

## Process

```mermaid
flowchart TD
  A[Define job: Label + ProgramArguments] --> B{GUI session needed?}
  B -->|per-user, login items, Aqua| C[LaunchAgent: ~/Library/LaunchAgents/label.plist]
  B -->|system-wide, boots before login| D[LaunchDaemon: /Library/LaunchDaemons/label.plist]
  C --> E[RunAtLoad true + KeepAlive tuned + ThrottleInterval >= 10s]
  D --> E
  E --> F[StandardOutPath/StandardErrorPath to a durable log dir, never /tmp]
  F --> G["Pin PATH: absolute ProgramArguments[0] or EnvironmentVariables.PATH"]
  G --> H[launchctl bootstrap gui/UID or system label.plist]
  H --> I{External supervision-integrity check}
  I -->|supervisor loaded, daemon reachable| J[ok]
  I -->|supervisor loaded, daemon unreachable| K[warn: kickstart the job]
  I -->|supervisor label absent: brew upgrade, logout, bootout| L[critical: nothing will resurrect it]
  L --> M[Re-bootstrap the supervisor, then verify again]
```

1. Decide Agent vs Daemon first: does the job need a logged-in GUI session (Aqua, notifications, keychain UI) or must it run before/without any user session? That answer decides the install location, not preference.
2. Write the plist: unique reverse-DNS `Label`, absolute-path `ProgramArguments`, `RunAtLoad=true` so it survives reboot/login, `KeepAlive` shaped to intent (bare `true`, or `{SuccessfulExit:false, Crashed:true}` to avoid restart-looping a clean exit), `ThrottleInterval >= 10` to stop thrash.
3. Route `StandardOutPath`/`StandardErrorPath` to a durable directory — `~/Library/Logs/<app>/` for an Agent, `/Library/Logs/<app>/` or `/var/log/<app>/` for a Daemon. Never `/tmp` or `/private/tmp`.
4. Pin the runtime environment: launchd jobs start with `PATH=/usr/bin:/bin:/usr/sbin:/sbin` and no shell profile. Use absolute paths in `ProgramArguments` or set `EnvironmentVariables.PATH` explicitly.
5. Lint the plan with `scripts/plist_lint.mjs` before you ever run `launchctl bootstrap`.
6. Load it with the modern `launchctl bootstrap gui/$(id -u) <plist>` (Agent) or `launchctl bootstrap system <plist>` (Daemon) — not the deprecated `load`/`unload`.
7. Add an EXTERNAL supervision-integrity check, separate from both the daemon and its KeepAlive stanza, that runs `launchctl list <label>` for every legitimate supervisor label on a schedule or on `doctor`/health-check invocation, and classifies severity (ok / warn / critical) — because KeepAlive only acts while the job is loaded, and it cannot detect its own absence.

## Output Contract

`scripts/plist_lint.mjs` returns:

- `pass` — boolean, true only when zero `critical` findings remain.
- `findings` — array of `{ severity: "critical"|"warning", code, message }`, one per detected defect.
- `recommendations` — array of concrete fix strings, one per finding, in the same order.

Use `scripts/plist_lint.mjs` to lint a launchd job plan (JSON matching `schemas/launchd-plan.schema.json`) and get a deterministic pass/fail with actionable findings before you ship a plist.

## Anti-Patterns

### KeepAlive Means It'll Always Restart

**Novice**: Sets `KeepAlive: true` and treats the daemon as permanently self-healing — "launchd's got it."
**Expert**: `KeepAlive` only supervises *while the job is loaded*. A `brew upgrade`, a user logout, a `launchctl bootout`, or a corrupted plist unloads the job entirely, and nothing resurrects the daemon until something re-bootstraps the supervisor itself. Port Daddy hit this for real: Homebrew's `KeepAlive` on `homebrew.mxcl.port-daddy` supervised the daemon perfectly — until `brew upgrade` churn unloaded the job, and the daemon died silently with zero supervisors watching. The fix (PR #607) was `pd doctor supervision-integrity`: an external, 3-tier severity check (`ok` / `warn` / `critical`) that queries `launchctl list <label>` for every legitimate supervisor label, independent of the daemon's own health.
**Detection**: A plist with `KeepAlive` set and no separate process, cron, or `doctor` command that periodically confirms the supervisor label itself is still loaded — grep for `KeepAlive` with no companion `launchctl list` call anywhere in the codebase.

### Logging To /tmp

**Novice**: `StandardOutPath` and `StandardErrorPath` point at `/tmp/mydaemon.log` "because logs are temporary anyway."
**Expert**: macOS purges `/tmp` and `/private/tmp` on a schedule and on every reboot. The one log that would explain a boot-time crash is gone before anyone reads it — especially cruel because boot-time failures are exactly when you need durable logs. Use `~/Library/Logs/<app>/` for a LaunchAgent or `/Library/Logs/<app>/` / `/var/log/<app>/` for a LaunchDaemon.
**Detection**: `grep -E 'StandardOutPath|StandardErrorPath' *.plist` shows a value starting with `/tmp` or `/private/tmp`.

### Trusting Shell PATH Inside A launchd Job

**Novice**: `ProgramArguments: ["node", "server.js"]`, assuming `node` resolves the way it does in Terminal.
**Expert**: launchd jobs start with a minimal environment (`PATH=/usr/bin:/bin:/usr/sbin:/sbin`) and never source `.zshrc`/`.bash_profile`. Homebrew-installed binaries, `nvm` shims, and anything on a custom `PATH` all 404 as "command not found" even though `which node` works fine interactively. Use an absolute path (`/opt/homebrew/bin/node`) as `ProgramArguments[0]`, or set `EnvironmentVariables.PATH` explicitly in the plist.
**Detection**: `ProgramArguments[0]` is a bare command name (no leading `/`) and the plist has no `EnvironmentVariables.PATH` key.

## References

| File | Load When |
| --- | --- |
| `references/plist-field-reference.md` | Need the exact meaning and failure semantics of each plist key before writing one. |
| `references/agent-vs-daemon-and-integrity.md` | Need to decide LaunchAgent vs LaunchDaemon, or design a supervision-integrity check. |
| `examples/expected-output.md` | Need a worked example: a real annotated plist plus its lint report. |
| `templates/output-template.md` | Need a reusable skeleton for a plist-plus-lint deliverable. |
| `schemas/launchd-plan.schema.json` | Need to validate a launchd job plan before running the linter. |
| `scripts/plist_lint.mjs` | Need deterministic linting of a launchd job plan. |
| `agents/openai.yaml` | Need a subagent descriptor for delegated launchd plist authoring/review. |

<!-- BEGIN BUNDLE INDEX (auto: index_references.py) -->

## Skill Bundle Index

*Every file in this skill, and when to open it. Auto-generated; run `scripts/index_references.py --fix`.*

**root**
- [`CHANGELOG.md`](CHANGELOG.md) — macOS Launchd Supervision — Changelog — - Initial skill creation - Core process defined - Reference files and deterministic plist_lint script added
- [`README.md`](README.md) — macOS Launchd Supervision — Author correct launchd plists for long-lived local daemons, and design the external supervision-integrity check that catches what `KeepAlive

**`agents/`**
- [`agents/openai.yaml`](agents/openai.yaml) — openai (data/schema)

**`examples/`**
- [`examples/expected-output.md`](examples/expected-output.md) — Example Output: macOS Launchd Supervision — Scenario: Port Daddy's own daemon (`pd start --foreground`) needs a LaunchAgent that survives `brew upgrade` churn, logs durably, and is che
- [`examples/sample-input.json`](examples/sample-input.json) — sample input (data/schema)

**`references/`**
- [`references/agent-vs-daemon-and-integrity.md`](references/agent-vs-daemon-and-integrity.md) — LaunchAgent vs LaunchDaemon, and the Supervision-Integrity Pattern — Use this when deciding where a job lives, or when designing a check that verifies the supervisor itself.
- [`references/plist-field-reference.md`](references/plist-field-reference.md) — Plist Field Reference — Use this when writing or reviewing the actual `.plist` keys, not just the high-level plan.

**`schemas/`**
- [`schemas/launchd-plan.schema.json`](schemas/launchd-plan.schema.json) — launchd plan.schema (data/schema)

**`scripts/`**
- [`scripts/plist_lint.mjs`](scripts/plist_lint.mjs)

**`templates/`**
- [`templates/output-template.md`](templates/output-template.md) — Launchd Supervision Plan — - Label: `[reverse-DNS label]` - Placement: `[agent | daemon]` — because `[GUI-session need / boot-time need]` Run: Paste the lint report an

<!-- END BUNDLE INDEX -->

Attribution

curiositechcuriositech
View sourceSee grades on GitHubMore from curiositech →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

ucoz-landing-skill

Create and edit uCoz homepage landing pages via MCP: custom templates, hero sections, lead forms, navigation menus, SEO, and responsive layout. Includes a visual design system (style selection, layout/grid, section recipes, typography/spacing, color tokens, component states, icons, modern CSS/JS, motion, imagery, social proof, copy/voice, accessibility). Uses ucoz-mcp tools for templates, site file uploads, and site modules.

107 votes

Paperclip

Interact with the Paperclip control plane API for task coordination and governance. Use when checking assignments, updating issue status, posting comments, delegating work, managing routines, or calling Paperclip API endpoints.

953191 votes

Pptx

Presentation toolkit (.pptx). Create/edit slides, layouts, content, speaker notes, comments, for programmatic presentation creation and modification.

471861 votes

Daw Music

Digital Audio Workstation usage, music composition, interactive music systems, and game audio implementation for immersive soundscapes.

761 votes

Instantly Rdsthomas Mission Control

Instantly.ai cold email outreach API - manage campaigns, leads, accounts, and analytics. Use for cold email automation, lead management, campaign creation/monitoring, and email account warmup.

761 votes
View all in tools →