Create Dart Background Activity Process
Workflow
Check repo and environment first.
- If the repo uses FVM, run Dart and Flutter commands through
fvm. - Prefer a checked-in executable entrypoint path such as
daemon/bin/<service_name>orbin/<service_name>.
- If the repo uses FVM, run Dart and Flutter commands through
Define the background process shape.
- Choose a stable launchd
Label, for examplecom.example.sync_agent. - Decide whether the process is:
- a pure Dart CLI launched with
dart run - an AOT-compiled Dart executable
- a wrapper script that resolves the Dart SDK and then
execs the real process
- a pure Dart CLI launched with
- Prefer an AOT executable or a checked-in launcher script over
/bin/zsh -lc ....
- Choose a stable launchd
Create the Dart entrypoint.
- Add or update a dedicated CLI entrypoint such as
bin/<service_name>.dart. - Keep startup deterministic:
- parse config from env vars or a config file
- initialize logging early
- trap termination signals if graceful shutdown matters
- keep the main isolate alive with the long-running task loop
- If the service needs restart safety, make failures exit non-zero and let launchd restart it.
- Add or update a dedicated CLI entrypoint such as
Create a real executable for launchd.
- Do not register
/bin/zsh,/bin/bash, orshasProgramArguments[0]. - Preferred options:
- AOT build:
dart compile exe bin/<service_name>.dart -o daemon/bin/<service_name> - Launcher script:
#!/bin/bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" exec dart run "$REPO_ROOT/bin/<service_name>.dart"
- AOT build:
- The executable path itself should represent the intended service identity so App Background Activity shows the right name.
- Do not register
Write the LaunchAgent plist.
- Store it at
~/Library/LaunchAgents/<label>.plistfor per-user jobs unless the user explicitly needs a system daemon. - Include at minimum:
LabelProgramArgumentswith the real executable as the first itemRunAtLoadKeepAliveif continuous restart is desiredWorkingDirectoryif the service depends on relative pathsStandardOutPathandStandardErrorPathfor debugging
- If environment variables are required, prefer
EnvironmentVariablesin the plist or explicit setup in the launcher script.
- Store it at
Load and verify the service.
- Unload any stale job before replacing the plist:
uid=$(id -u) launchctl bootout "gui/$uid/<label>" >/dev/null 2>&1 || true - Load the service:
launchctl bootstrap "gui/$uid" "$HOME/Library/LaunchAgents/<label>.plist" - Verify launchd state:
launchctl print "gui/$uid/<label>" plutil -p "$HOME/Library/LaunchAgents/<label>.plist" - Confirm
programorarguments[0]points to the intended executable path, not a shell wrapper.
- Unload any stale job before replacing the plist:
Validate runtime behavior.
- Check service logs:
tail -n 100 "$HOME/Library/Logs/<app>/<stderr-log>" - Confirm the process is running with the expected name:
ps aux | rg "<service_name>" - If the process exposes ports, inspect listeners separately:
lsof -nP -iTCP:<port> -sTCP:LISTEN - Distinguish launchd registration problems from Dart runtime failures such as missing SDK paths, bad env vars, or application exceptions.
- Check service logs:
Reflect the result back into source code.
- Commit the Dart entrypoint, launcher, plist template, and any service-management scripts together.
- If the repo generates plists automatically, update the generator so future installs keep the same executable-first pattern.
- If this renames an existing service, call out the compatibility impact clearly because installed plist names, labels, and process names will change.
Notes
- App Background Activity naming follows the executable launchd registers, not only the final child process title.
exec -ais not a durable fix ifProgramArguments[0]is still a shell.- For macOS user agents,
~/Library/LaunchAgentsis the default target; useLaunchDaemonsonly when the user explicitly needs a machine-wide service. - If the repo ships a launcher script, keep it stable and executable so plist updates do not silently drift from the checked-in source.