launchd
Launchd is the init and service management daemon in Apple's Darwin-based operating systems, such as macOS and iOS, serving as the first user-space process (PID 1) executed by the kernel after boot to complete system initialization and manage the lifecycle of background processes known as daemons and agents.[1][2][3] Introduced with Mac OS X 10.4 Tiger in 2005, launchd superseded the BSD-style init process and SystemStarter subsystem, offering a more efficient, on-demand approach to process launching that minimizes resource consumption by starting services only when required.[2] It configures jobs through XML property list (.plist) files, which define essential keys such as Label for unique identification, ProgramArguments for executable paths and parameters, and optional directives like KeepAlive for automatic relaunching, StartInterval for periodic execution, or WatchPaths for file-based triggering.[2] System-wide daemons, stored in directories like/System/Library/LaunchDaemons or /Library/LaunchDaemons, run independently of user logins and often require elevated privileges, whereas user agents in ~/Library/LaunchAgents or /Library/LaunchAgents execute within a specific user's session for personalized tasks.[1][2]
Notable features include socket activation for network services, emulation of legacy inetd behavior, and integration with the launchctl command-line utility for loading, unloading, and monitoring jobs, all contributing to enhanced performance, security, and administrative simplicity in macOS environments.[1][2]
Introduction
Overview
launchd is an init and service management daemon developed by Apple Inc. for handling system initialization, launching services, and scheduling jobs on Darwin-based operating systems, including macOS, iOS, and watchOS.[4] It serves as the primary mechanism for managing background processes, such as daemons that run system-wide and agents that operate in user sessions.[4] Introduced on April 29, 2005, as part of Mac OS X 10.4 Tiger, launchd replaced several legacy components to streamline system startup and task management.[5] Specifically, it supplanted the BSD-style init process for boot-time initialization, SystemStarter for service orchestration, and cron for periodic task execution, including daemons, agents, and scheduled jobs.[4] At its core, launchd emphasizes on-demand loading of jobs, which activates services only when needed to enhance boot performance and resource utilization.[4] Implemented in C, it runs as process ID 1 (PID 1) on macOS, assuming responsibility for the initial user-space environment after kernel boot.[4] In operation, launchd parses property list files during system boot or user login to register and oversee job execution.[4]Role in Operating Systems
launchd serves as the primary init system in macOS, responsible for system initialization following kernel boot by loading and managing system-level daemons from directories such as/System/Library/LaunchDaemons/ and /Library/LaunchDaemons/.[2] It handles ongoing service supervision, including on-demand launching, resource registration for sockets and file descriptors, and automatic relaunching of failed processes to ensure system reliability.[2] In macOS, launchd operates in distinct environments: a system-wide instance running as root for global services and per-user instances activated upon login to manage user-specific agents from directories like ~/Library/LaunchAgents.[2]
Within the broader Apple ecosystem, launchd is integral to Darwin, the open-source Unix-like core underlying iOS, watchOS, and tvOS, where it manages system daemons in embedded and mobile contexts. However, adaptations in these platforms impose restrictions, such as prohibiting user-level agents to align with sandboxing and security models that limit background process execution outside app boundaries.[3] launchd also interacts with macOS kernel extensions (kexts) by supervising user-space services such as kextd that load or configure them, though direct kernel loading is handled by the kernel's mechanisms.[6]
Efforts to port launchd beyond Apple systems began with a 2005 Google Summer of Code project by R. Tyler Croy, which adapted it for FreeBSD as a session init but not as PID 1 due to Mach dependencies.[7] In 2015, NextBSD incorporated launchd alongside Darwin components, implementing a compatibility layer for Mach APIs to enable its use in a FreeBSD fork aimed at NeXTSTEP-inspired features.[8] Adoption in other Unix-like systems remains limited, primarily owing to launchd's reliance on Apple-specific elements like Mach services, which complicate full portability without significant modifications.[7]
Since macOS 10.11 El Capitan, launchd's operation has been influenced by System Integrity Protection (SIP), which safeguards system directories including /System/Library/LaunchDaemons/ against unauthorized modifications, even by root, to enhance security and prevent tampering with core services.[9] This evolution maintains launchd's foundational role in the Darwin kernel while enforcing stricter protections on configuration and execution in protected environments.[9]
Core Components
The launchd Daemon
The launchd daemon functions as the primary init process in macOS, initiated by the kernel as process ID 1 (PID 1) during system boot to orchestrate the startup of background services and maintain system stability.[4] As the root process, it inherits control from the kernel and runs a single system-wide instance to oversee global operations, complemented by per-user instances that activate upon login to manage session-specific tasks.[4] This architecture ensures robust service management across both system and user contexts without requiring manual intervention for basic initialization. In its supervisory role, launchd loads jobs defined in property list files and oversees their full lifecycle, including startup, execution, and orderly termination.[10] It continuously monitors active jobs for failures, leveraging the KeepAlive key to automatically restart them upon exit or crash, thereby promoting service reliability and minimizing downtime.[2] This monitoring extends to tracking job states and resource consumption, allowing launchd to intervene as needed to sustain operational integrity. Resource management is a core aspect of launchd's runtime behavior, achieved through lazy loading mechanisms that delay job activation until an explicit trigger occurs, thereby conserving memory and CPU resources during idle periods.[4] For inter-process communication, launchd employs Mach ports within the bootstrap namespace, enabling seamless coordination between the daemon and client processes.[10] Error handling in launchd emphasizes diagnostics and recovery, with events and anomalies logged via the unified logging system for systematic review and debugging.[4][11] It processes job exit codes to determine success or failure states, integrating with macOS's broader crash reporting infrastructure to capture and report daemon-related incidents when they arise.[12] This layered approach ensures that operational disruptions are contained and addressable without compromising overall system function.launchctl Command-Line Tool
launchctl serves as the primary command-line interface for interacting with the launchd daemon on macOS, enabling users and administrators to load, unload, start, stop, query, and manage jobs defined in property list configuration files.[1] It operates via inter-process communication (IPC), historically using theLAUNCHD_SOCKET environment variable to locate the appropriate launchd instance, though this has been superseded by XPC services in modern macOS versions for secure, domain-specific interactions.[13] The tool supports both legacy and contemporary subcommands, allowing fine-grained control over daemons and agents across system and user domains without requiring a system reboot in most cases.[14]
Key legacy subcommands include load and unload for importing or removing property list files into launchd, start and stop (or kill for signaling) to manually invoke or halt jobs by their label, and list to display the status of loaded jobs, including PID, exit status, and last exit code.[2] For example, the syntax launchctl load /Library/LaunchDaemons/com.example.service.plist loads a system daemon plist, while launchctl start com.example.service initiates the job if already loaded.[15] Since macOS 10.10 (Yosemite), Apple introduced enhanced subcommands like bootstrap and bootout to load or unload services with explicit domain targeting (e.g., system or gui/<uid>), enable and disable to toggle job activation persistently across reboots, and kickstart to restart a service, often eliminating the need for sudo in user contexts.[14] An example modern usage is launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.agent.plist to load a user agent, or launchctl disable system/com.example.service to prevent a daemon from running.[14] Certain legacy flags and behaviors, such as unrestricted domain access, were deprecated post-10.10 to improve security and namespace isolation.[14]
For debugging, launchctl offers verbose output via the -v flag in subcommands like load and unload to report detailed loading status, and the debug subcommand to configure services with options like --stdout and --stderr for redirecting logs to files, or environment flags such as --NSZombie for memory debugging.[16] Additionally, integration with the sysdiagnose utility allows generation of comprehensive diagnostic reports that include launchd job states, logs, and errors, triggered via sudo sysdiagnose or keyboard shortcut (Option-Command-Shift-Period in Recovery Mode), aiding in troubleshooting persistent issues. These features facilitate monitoring and resolution without altering core launchd behavior.[14]
Property List Configuration Files
Property list configuration files, commonly referred to as plists, serve as the primary mechanism for defining launchd jobs on macOS and other Darwin-based systems. These files are structured in Apple's property list format, which supports both XML and binary representations, and must consist of a root dictionary containing essential keys to specify the job's behavior. TheLabel key provides a unique string identifier for the job, ensuring it can be distinctly referenced across the system, while the Program key specifies the executable path as a string, or alternatively, the ProgramArguments key defines an array where the first element is the executable and subsequent elements are its arguments. At minimum, either Program or ProgramArguments must be present to define the job's entry point.[2][17]
Launchd plists are stored in designated directories based on the job's scope and privilege level. System-wide daemons, which run as root and affect the entire machine, reside in /System/Library/LaunchDaemons for built-in services or /Library/LaunchDaemons for those installed by administrators. User-specific agents, which operate under the user's context, are placed in ~/Library/LaunchAgents for per-user configurations, /Library/LaunchAgents for system-wide user jobs, or /System/Library/LaunchAgents for core system agents. These locations ensure appropriate isolation between system and user environments.[2][17]
Upon system boot for daemons or user login for agents, the launchd process automatically scans these directories for files ending in .plist and parses them into memory. During this process, launchd validates each plist against its internal schema; files with syntactic errors, missing mandatory keys like Label, or invalid structures are silently ignored to prevent system instability. This parsing occurs without manual intervention, loading valid jobs into active domains for subsequent management.[2]
File permissions play a critical role in security and sandboxing for launchd plists. System plists in /System and /Library directories must be owned by root with restrictive modes such as 644 or 600, prohibiting group or world writability to avoid unauthorized modifications. User agents in ~/Library are owned by the respective user with similar restrictive permissions, aligning with macOS's sandboxing model that confines job execution to the owner's privileges and prevents escalation. Violations in ownership or permissions result in the plist being ignored during scanning.[2][17]
Basic validation emphasizes the presence of core keys to ensure functional jobs. The Label is mandatory and must be unique within its launch domain to avoid conflicts, while ProgramArguments (or Program) is required to specify the executable. Optional sections, such as arrays for arguments or dictionaries for additional metadata, enhance configurability but do not affect basic loadability. This structure promotes reliability by enforcing minimal viable configurations before deeper job activation.[2][17]
Service Management
Daemons and Agents
In launchd, jobs are categorized as daemons or agents based on their scope, privileges, and execution context. Daemons are system-wide processes that operate with root privileges and are loaded at system boot, independent of user logins. They are typically stored in/System/Library/LaunchDaemons/ for system-provided daemons and /Library/LaunchDaemons/ for administrator-installed daemons and handle critical infrastructure tasks, such as network services that must run continuously to support the entire system.[2]
Agents, in contrast, are user-specific jobs that execute in the context of a logged-in user, inheriting the user's environment variables and session. They are loaded from /System/Library/LaunchAgents/, /Library/LaunchAgents/, and ~/Library/LaunchAgents/ upon user login and can interact with graphical user interfaces (GUI) if required, making them suitable for per-user background tasks like application helpers or notifications.[2]
Key differences between daemons and agents include their persistence and security profiles. Daemons continue running across user logins and reboots, ensuring system stability, while agents are tied to individual user sessions and terminate when the user logs out. For security, daemons are often confined using sandboxing via entitlements in their property list files to limit potential damage from vulnerabilities, whereas agents operate with user-level privileges and may leverage the user's sandbox if part of a sandboxed application.[18]
Hybrid scenarios, though rare, allow per-user daemons by configuring LaunchAgents to escalate privileges, such as by using sudo in the ProgramArguments; however, this approach is discouraged due to security risks and is not natively supported by launchd.[19]
Launchd facilitates migration from legacy systems by replacing traditional /etc/rc startup scripts and cron jobs with daemon or agent configurations, enabling on-demand execution that conserves resources compared to always-running processes in older methods.[20]
Launch Domains
Launch domains in launchd act as hierarchical namespaces that organize and scope jobs, ensuring isolation and appropriate execution contexts for system-wide, user-specific, or session-based services. These domains define the boundaries within which property list configuration files are loaded and managed, preventing cross-domain interference while allowing controlled inheritance of environmental settings.[2][14] The primary domain types include the system domain, user domains, and session domains. The system domain operates at the root level and is boot-loaded, managing global daemons from directories such as/System/Library/LaunchDaemons and /Library/LaunchDaemons; it provides services accessible across the entire system regardless of user sessions.[2] User domains are tied to individual user IDs (UIDs) and are login-loaded, handling per-user agents from paths like /System/Library/LaunchAgents/, /Library/LaunchAgents, and ~/Library/LaunchAgents; a separate launchd instance runs for each user to enforce personalized scoping.[2] Session domains are more granular, often app-specific or tied to login types, such as GUI sessions created upon graphical login.[21]
In the domain hierarchy, within a user domain, configurations from later-loaded directories (such as ~/Library/LaunchAgents) can override those from earlier ones (such as /System/Library/LaunchAgents) if they share the same Label, allowing user-specific customizations. User domains receive environment settings from the login session, separate from the system domain.[2] This structure supports multi-user environments by running isolated launchd instances per UID, with automatic cleanup of user and session jobs upon logout to free resources and prevent lingering processes.[2]
Management of domains occurs primarily through the launchctl command-line tool, which includes subcommands like bootstrap and bootout to load or unload jobs into targeted domains, such as system for root-level operations or gui/<uid> for user GUI sessions.[14] This isolation mechanism ensures that actions in one domain, like a user's session, do not affect others, enhancing security in multi-user setups. Special domains include the Aqua domain for GUI-specific sessions, identified by the graphical login context, and per-user domains prefixed like com.apple.launchd, which handle individualized agent loading.[21][22] Common pitfalls arise from domain mismatches, such as attempting to load a user agent into the system domain, resulting in errors like "Could not find domain for" and job failures due to improper scoping.[23][14]
Activation Mechanisms
On-Demand Launching
On-demand launching in launchd enables jobs to remain unloaded and inactive until explicitly triggered by system events, such as time-based schedules, thereby minimizing resource consumption and accelerating system boot times. This mechanism ensures that daemons and agents are started only when required, allowing them to terminate after completing their tasks and be relaunched on subsequent needs, which has been the default behavior for launchd jobs since macOS 10.4.[2] Time-based triggers form the core of on-demand launching, with two primary options defined in the job's property list file. TheStartInterval key specifies a periodic interval in seconds after which the job launches; for instance, setting it to 300 results in execution every five minutes, and if the system is asleep, the job activates upon wake-up with coalesced intervals to avoid overlaps. Since macOS 10.9, launchd coalesces multiple pending timers within short intervals (typically around 10 seconds) to conserve power, unless overridden by the ThrottleInterval key.[17] The StartCalendarInterval key provides cron-like precision through a dictionary of calendar components, including Hour (0-23), Minute (0-59), Day (1-31), Weekday (0-7, where 0 and 7 are Sunday), and Month (1-12), with unspecified values acting as wildcards; multiple pending intervals during sleep also coalesce into a single invocation upon resumption.[17]
For on-demand jobs with KeepAlive set to false (the default), launchd allows jobs to exit after running and restarts them only on the next trigger; if a job terminates unexpectedly during execution, it will not be relaunched until the subsequent trigger. Configurable timeouts include TimeOut, which specifies a recommended idle duration in seconds that launchd passes to the job for its own timeout logic, and ExitTimeOut, which sets the grace period in seconds before sending a SIGKILL signal when launchd terminates the job (defaulting to 20 seconds, or infinite if 0).[2][17]
The OnDemand key, introduced in macOS 10.4 to toggle whether jobs were kept persistently running (default true), was deprecated and removed in macOS 10.5 and later, with on-demand launching now achieved by default through the absence or explicit false value of KeepAlive.[17]
This approach yields significant benefits, including reduced memory and CPU overhead by avoiding constant execution of idle services, as well as simplified management without the need for complex startup dependencies or elevated privileges. Common applications include periodic maintenance tasks, such as database cleanups or system diagnostics, where jobs run briefly at scheduled intervals to perform housekeeping without persistent resource allocation.[2]
Socket Activation
Socket activation is a mechanism in launchd that enables on-demand launching of network services by having launchd manage listening sockets on behalf of jobs. When a connection arrives at a configured socket, launchd starts the associated job if it is not already running and provides the job with the relevant file descriptors, allowing the service to handle the request without needing to bind ports itself. This approach conserves system resources by keeping services dormant until needed and ensures sockets remain available to clients at all times.[2] Configuration for socket activation occurs within theSockets dictionary of a job's property list file. Each entry in this dictionary is named by the developer (e.g., "Listeners" or "HTTP") and contains a sub-dictionary specifying socket parameters derived from getaddrinfo(3). Key options include SockServiceName for the service name or port number (referencing /etc/services), SockType set to "stream" for TCP or "dgram" for UDP (defaulting to "stream"), and SockFamily as "IPv4", "IPv6", or implied "Unix" for domain sockets via SockPath. For instance, a TCP listener on port 80 might use SockServiceName as "http" with SockType as "stream" and SockFamily as "IPv4". Multiple socket groups can be defined, enabling a single job to handle various protocols or ports.[2][24]
The activation process begins with launchd creating and binding to the specified sockets upon loading the job, typically running with elevated privileges to access low-numbered ports like 80. Upon an incoming connection, launchd launches the job and queues the connection. The job must then invoke the launch_activate_socket function, passing the socket name from the plist, to retrieve an array of file descriptors (int **fds) and their count (size_t *cnt), which the caller frees after use. This API ensures the job receives all relevant descriptors for the socket group, supporting scenarios with multiple fds per activation. Once obtained, the job can perform operations like accept(2) on the descriptors to service connections. If the job exits after handling requests, launchd reclaims the sockets and awaits the next activation.[24]
launchd's socket activation does not include native dependency resolution, requiring jobs to manage inter-service coordination via interprocess communication (IPC) mechanisms. It also lacks socket-specific conditions for activation, limiting flexibility to basic connection triggers without additional qualifiers like time-based or resource checks.[17]
Common use cases include network daemons such as DHCP servers configured with SockServiceName as "bootps" and SockType as "dgram" for UDP-based broadcasts, where the service activates only on client requests to conserve idle resources. Web servers represent another key application, starting on HTTP or HTTPS connections to ports 80 or 443, which facilitates zero-downtime restarts: during a job reload, launchd maintains the listening socket, queuing new connections until the updated job retrieves the descriptors and resumes operation. This on-demand model aligns with launchd's broader principle of deferred execution for efficiency in resource-constrained environments.[2]
Path and Mach Port Monitoring
Launchd supports path monitoring to activate jobs in response to filesystem events, primarily through theWatchPaths and QueueDirectories keys defined in property list configuration files. The WatchPaths key accepts an array of strings representing file or directory paths to monitor for changes, such as creations, modifications, or deletions. When any specified path is altered, launchd triggers the associated job to start, enabling reactive behaviors like processing updated configuration files. For instance, monitoring /etc/hostconfig can launch a service whenever host settings are modified.[2]
In contrast, the QueueDirectories key targets directories and activates jobs only when the directory becomes non-empty due to added files, while keeping the job running until the directory is emptied again. This mechanism suits queue-based processing, such as handling incoming mail in /var/spool/mymailqdir, where the job processes items and removes them to signal completion. Both keys rely on kernel-level event notifications via kqueues, which provide efficient, real-time detection of filesystem changes without recursive monitoring of subdirectories.[2]
For inter-process communication, launchd enables Mach port monitoring through the MachServices key, a dictionary that registers named Mach ports for services. When a client process requests a service via bootstrap_look_up on the bootstrap port, launchd activates the corresponding job if it is not already running, passing the port rights to the job for message handling. This activation supports lightweight IPC in macOS, particularly for XPC services where messages trigger daemon responses without constant resource use. Options like ResetAtClose ensure port cleanup on job exit, while HideUntilCheckIn delays registration until the job explicitly checks in.
Common use cases for path monitoring include backup tools that activate on file modifications to synchronize data, while Mach port activation facilitates notifications between processes, such as system daemons signaling user agents. However, path monitoring incurs overhead for high-frequency changes and lacks support for recursive directory watching, potentially requiring multiple entries for broad coverage. Privacy protections in macOS may also prevent monitoring of sensitive paths, causing jobs to fail loading.
Configuration Details
Plist File Structure
Launchd configuration files, known as property list (plist) files, are structured as XML documents or their binary equivalents, adhering to the standard Apple property list format. The root element is a dictionary (<dict> in XML), which encapsulates all job parameters as key-value pairs, ensuring a hierarchical organization that supports nested elements for complex configurations. This structure allows launchd to parse and load jobs efficiently, with the dictionary serving as the container for essential and optional keys that define the job's behavior and execution environment.[2][17][25]
At the root level, the dictionary typically includes core elements such as the Label key, which is a required unique string identifier for the job; Program or ProgramArguments, where Program is an optional string specifying the executable path and ProgramArguments is a required array of strings listing the command and its arguments if Program is absent; and WorkingDirectory, an optional string setting the job's working directory. Inner sections expand this hierarchy with specialized dictionaries and arrays: for instance, KeepAlive and RunAtLoad are optional dictionaries or booleans controlling job lifecycle, ThrottleInterval is an optional number specifying restart delays in seconds, and EnvironmentVariables is a dictionary of key-value pairs for setting environment variables. These nested structures enable modular configuration without flattening the plist into a single level.[2][17]
Launchd plists support standard data types defined in the property list specification, including strings for paths and labels, numbers (integers or reals) for intervals and limits, booleans for flags like enabling or disabling features, and dates for scheduling via keys like StartCalendarInterval. Dictionaries hold unordered key-value mappings, while arrays maintain ordered collections, such as for argument lists. Plists can be stored in human-readable XML format, starting with <?xml version="1.0" encoding="UTF-8"?> and using tags like <string>value</string>, or in a compact binary format for efficiency, with both interchangeable via conversion tools.[25][2]
Validation of plist files enforces strict rules to prevent loading errors: dictionaries must not contain duplicate keys, as this would lead to undefined behavior during parsing; the Label key is mandatory and must be globally unique within its launch domain to avoid conflicts; and malformed plists, such as those with invalid data types or syntax errors, result in launchd logging failures and refusing to load the job, often with details accessible via system logs or debug modes. Error handling typically involves diagnostic output from launchd, highlighting issues like type mismatches or missing required elements.[25][17][2]
For editing and validation, the plutil command-line tool is essential, supporting conversion between XML and binary formats (e.g., plutil -convert binary1 file.plist), syntax checking (e.g., plutil -lint file.plist), and output in various formats for inspection. Additionally, Xcode provides a graphical interface through its Property List Editor, allowing visual creation, modification, and validation of plist files with real-time error detection. These tools ensure compliance with the plist specification before loading via launchctl.[25][2]
Key Configuration Options
Launchd configuration files, known as property list (plist) files, use specific keys to define how jobs—such as daemons or agents—behave when loaded and executed. These keys are organized within an XML-structured plist and dictate essential identification, execution parameters, runtime conditions, resource allocation, and logging. Essential keys provide the core setup for identifying and running the job, while behavior keys control launch triggers and persistence. Resource and advanced keys further refine execution context, security, and output handling.[26][2] The Label key is a required string that uniquely identifies the job within launchd, following a reverse-DNS convention like "com.example.myjob" to avoid conflicts. It serves as the job's primary identifier, and the plist file is conventionally named after it with a ".plist" extension. The ProgramArguments key, an array of strings, specifies the executable and its command-line arguments; the first element is typically the program path or name, followed by arguments, and it is required unless the deprecated Program key is used. For instance, to run a script with arguments, it might be defined as:This ensures the job launches with the exact argument vector provided. The UserName key, an optional string, designates the user account under which the job runs, applicable only in the system domain for daemons; it defaults to root if unspecified but is ignored for user agents. Similarly, the GroupName key, also optional and a string, sets the group under which the process runs, defaulting to the user’s primary group when UserName is specified, and is limited to system domain jobs.[26] Behavior keys manage how and when the job launches and persists. The KeepAlive key, a boolean or dictionary, determines if the job should restart automatically upon exit; as a boolean true, it runs continuously, but as a dictionary, it supports conditions like SuccessfulExit (boolean for restarts on clean exits) or PathState (dictionary monitoring file existence or modification). Setting it to true implicitly enables RunAtLoad, but unconditional use is discouraged in favor of on-demand mechanisms to optimize system resources. The RunAtLoad key, a boolean defaulting to false, triggers an immediate launch upon plist loading, though it can strain boot or login times and is best avoided for non-essential jobs. StartInterval, an integer in seconds, schedules periodic launches (e.g., 3600 for hourly), but skips intervals during system sleep without calendar awareness. The WatchPaths key, an array of strings, monitors file paths for modifications to trigger launches, yet it is highly prone to race conditions where changes might be missed, making it unreliable for critical tasks.[26][2] Resource keys influence how launchd allocates system resources and restricts job visibility. The ProcessType key, a string such as "Background" or "Interactive", classifies the job to apply appropriate resource limits; "Background" throttles CPU and I/O to prevent interference with foreground tasks, while "Interactive" prioritizes responsiveness for user-facing processes. The LimitLoadToSessionType key, a string or array (e.g., "Aqua" for GUI sessions or "Background" for non-interactive), confines agent jobs to specific session types, with no effect on system daemons and defaulting to all types if omitted.[26] Advanced keys handle environmental and output specifics. The EnvironmentVariables key, a dictionary of string key-value pairs, injects custom variables into the job's environment before execution, overriding system defaults where applicable but ignoring non-string values. For logging, StandardErrorPath and StandardOutPath, both optional strings, redirect stderr and stdout to files (e.g., "/var/log/myjob.out"), creating them with permissions based on the job's user and group if they do not exist. These paths should use absolute references to ensure reliability.[26][2] Best practices for these keys emphasize efficiency and maintainability: avoid hardcoding absolute paths in ProgramArguments by leveraging environment variables or relative resolutions; use the MachServices key (a dictionary advertising Mach ports for IPC) to enable socket-based on-demand activation rather than persistent running; and note that the OnDemand key has been deprecated since macOS 10.5 in favor of KeepAlive or other conditional options. Always test configurations with<key>ProgramArguments</key> <array> <string>/usr/bin/my_script</string> <string>arg1</string> <string>arg2</string> </array><key>ProgramArguments</key> <array> <string>/usr/bin/my_script</string> <string>arg1</string> <string>arg2</string> </array>
launchctl to verify behavior without system-wide impact.[26][2]