launchd: wait for /nix/store before starting agent (#8609)

On Darwin, launchd may attempt to start agents before the Nix store is
mounted and available. This leads to failures when the agent's executable
or arguments reside in the Nix store.

This change wraps the agent's command in a shell script that uses
/bin/wait4path to ensure /nix/store is ready before executing the
original program. It also ensures that ProgramArguments are correctly
escaped and concatenated.
This commit is contained in:
Wael Nasreddine 2026-01-27 21:51:48 -08:00 committed by GitHub
parent ef5da06269
commit eec72f1278
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 123 additions and 90 deletions

View file

@ -43,7 +43,31 @@ let
};
};
toAgent = config: pkgs.writeText "${config.Label}.plist" (toPlist { escape = true; } config);
# mutateConfig calls /bin/sh with /bin/wait4path to wait for /nix/store before
# running the original Program and ProgramArguments. This is intentional to
# fix the issue where launchd starts the agent before /nix/store is ready
# (before the Nix store is mounted.)
mutateConfig =
cnf:
let
args =
lib.optional (cnf.Program != null) cnf.Program
++ lib.optionals (cnf.ProgramArguments != null) cnf.ProgramArguments;
in
(removeAttrs cnf [
"Program"
"ProgramArguments"
])
// {
ProgramArguments = [
"/bin/sh"
"-c"
"/bin/wait4path /nix/store && exec ${lib.escapeShellArgs args}"
];
};
toAgent =
config: pkgs.writeText "${config.Label}.plist" (toPlist { escape = true; } (mutateConfig config));
agentPlists = lib.mapAttrs' (n: v: lib.nameValuePair "${v.config.Label}.plist" (toAgent v.config)) (
lib.filterAttrs (n: v: v.enable) cfg.agents

View file

@ -0,0 +1,13 @@
{ config, pkgs, ... }:
{
time = "2026-01-27T21:41:51+00:00";
condition = pkgs.stdenv.hostPlatform.isDarwin && config.launchd.enable;
message = ''
The `launchd` module now ensures that the Nix store is mounted and
available before starting any agents. This improves reliability on macOS
where `launchd` might otherwise attempt to start agents before the Nix
store is ready.
'';
}

View file

@ -17,9 +17,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>/some/command</string>
<string>--with-arguments</string>
<string>foo</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec /some/command --with-arguments foo</string>
</array>
<key>UnrecognizedByHomeManager</key>
<string>should make it to the resulting plist</string>

View file

@ -6,8 +6,12 @@
<true/>
<key>Label</key>
<string>org.nix-community.home.aerospace</string>
<key>Program</key>
<string>/nix/store/00000000000000000000000000000000-aerospace/Applications/AeroSpace.app/Contents/MacOS/AeroSpace</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec /nix/store/00000000000000000000000000000000-aerospace/Applications/AeroSpace.app/Contents/MacOS/AeroSpace</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>

View file

@ -6,13 +6,9 @@
<string>org.nix-community.home.git-maintenance-daily</string>
<key>ProgramArguments</key>
<array>
<string>@git@/bin/git</string>
<string>for-each-repo</string>
<string>--keep-going</string>
<string>--config=maintenance.repo</string>
<string>maintenance</string>
<string>run</string>
<string>--schedule=daily</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @git@/bin/git for-each-repo --keep-going &apos;--config=maintenance.repo&apos; maintenance run &apos;--schedule=daily&apos;</string>
</array>
<key>StartCalendarInterval</key>
<array>

View file

@ -6,13 +6,9 @@
<string>org.nix-community.home.git-maintenance-hourly</string>
<key>ProgramArguments</key>
<array>
<string>@git@/bin/git</string>
<string>for-each-repo</string>
<string>--keep-going</string>
<string>--config=maintenance.repo</string>
<string>maintenance</string>
<string>run</string>
<string>--schedule=hourly</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @git@/bin/git for-each-repo --keep-going &apos;--config=maintenance.repo&apos; maintenance run &apos;--schedule=hourly&apos;</string>
</array>
<key>StartCalendarInterval</key>
<array>

View file

@ -6,13 +6,9 @@
<string>org.nix-community.home.git-maintenance-weekly</string>
<key>ProgramArguments</key>
<array>
<string>@git@/bin/git</string>
<string>for-each-repo</string>
<string>--keep-going</string>
<string>--config=maintenance.repo</string>
<string>maintenance</string>
<string>run</string>
<string>--schedule=weekly</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @git@/bin/git for-each-repo --keep-going &apos;--config=maintenance.repo&apos; maintenance run &apos;--schedule=weekly&apos;</string>
</array>
<key>StartCalendarInterval</key>
<array>

View file

@ -6,9 +6,9 @@
<string>org.nix-community.home.nh-clean</string>
<key>ProgramArguments</key>
<array>
<string>@nh@/bin/nh</string>
<string>clean</string>
<string>user</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @nh@/bin/nh clean user</string>
</array>
<key>StartCalendarInterval</key>
<array>

View file

@ -15,20 +15,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@opencode@/bin/opencode</string>
<string>web</string>
<string>--hostname</string>
<string>0.0.0.0</string>
<string>--port</string>
<string>4096</string>
<string>--mdns</string>
<string>--cors</string>
<string>https://example.com</string>
<string>--cors</string>
<string>http://localhost:3000</string>
<string>--print-logs</string>
<string>--log-level</string>
<string>DEBUG</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @opencode@/bin/opencode web --hostname 0.0.0.0 --port 4096 --mdns --cors https://example.com --cors http://localhost:3000 --print-logs --log-level DEBUG</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -8,8 +8,12 @@
<string>org.nix-community.home.sketchybar</string>
<key>ProcessType</key>
<string>Interactive</string>
<key>Program</key>
<string>/nix/store/00000000000000000000000000000000-sketchybar/bin/sketchybar</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec /nix/store/00000000000000000000000000000000-sketchybar/bin/sketchybar</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>

View file

@ -8,9 +8,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@borgmatic@/bin/borgmatic</string>
<string>--stats</string>
<string>--list</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @borgmatic@/bin/borgmatic --stats --list</string>
</array>
<key>StandardErrorPath</key>
<string>/home/hm-user/Library/Logs/borgmatic/launchd-stderr.log</string>

View file

@ -13,12 +13,9 @@
<string>org.nix-community.home.colima-default</string>
<key>ProgramArguments</key>
<array>
<string>@colima@/bin/colima</string>
<string>start</string>
<string>default</string>
<string>-f</string>
<string>--activate=true</string>
<string>--save-config=false</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @colima@/bin/colima start default -f &apos;--activate=true&apos; &apos;--save-config=false&apos;</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -13,8 +13,9 @@
<string>org.nix-community.home.emacs</string>
<key>ProgramArguments</key>
<array>
<string>@emacs@/bin/emacs</string>
<string>--fg-daemon</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @emacs@/bin/emacs --fg-daemon</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -18,8 +18,9 @@
<string>org.nix-community.home.espanso</string>
<key>ProgramArguments</key>
<array>
<string>@espanso@/Applications/Espanso.app/Contents/MacOS/espanso</string>
<string>launcher</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @espanso@/Applications/Espanso.app/Contents/MacOS/espanso launcher</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -8,7 +8,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@git-sync@/bin/git-sync</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @git-sync@/bin/git-sync</string>
</array>
<key>StartInterval</key>
<integer>500</integer>

View file

@ -20,8 +20,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@gpg@/bin/gpg-agent</string>
<string>--supervised</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @gpg@/bin/gpg-agent --supervised</string>
</array>
<key>RunAtLoad</key>
<false/>

View file

@ -8,7 +8,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>/nix/store/00000000000000000000000000000000-home-manager-auto-expire</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec /nix/store/00000000000000000000000000000000-home-manager-auto-expire</string>
</array>
<key>StandardErrorPath</key>
<string>/home/hm-user/Library/Logs/home-manager-auto-expire/launchd-stderr.log</string>

View file

@ -17,9 +17,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@goimapnotify@/bin/goimapnotify</string>
<string>-conf</string>
<string>/nix/store/00000000000000000000000000000000-imapnotify-hm-example.com-config.json</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @goimapnotify@/bin/goimapnotify -conf /nix/store/00000000000000000000000000000000-imapnotify-hm-example.com-config.json</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -11,10 +11,9 @@
<string>org.nix-community.home.remap-keys</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/hidutil</string>
<string>property</string>
<string>--set</string>
<string>{ &quot;UserKeyMapping&quot;: [ { &quot;HIDKeyboardModifierMappingSrc&quot;: 0x700000039, &quot;HIDKeyboardModifierMappingDst&quot;: 0x70000002A } ] }</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec /usr/bin/hidutil property --set &apos;{ &quot;UserKeyMapping&quot;: [ { &quot;HIDKeyboardModifierMappingSrc&quot;: 0x700000039, &quot;HIDKeyboardModifierMappingDst&quot;: 0x70000002A } ] }&apos;</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -6,8 +6,9 @@
<string>org.nix-community.home.nix-gc</string>
<key>ProgramArguments</key>
<array>
<string>@nix@/bin/nix-collect-garbage</string>
<string>--delete-older-than 30d</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @nix@/bin/nix-collect-garbage &apos;--delete-older-than 30d&apos;</string>
</array>
<key>StartCalendarInterval</key>
<array>

View file

@ -24,8 +24,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@ollama@/bin/ollama</string>
<string>serve</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @ollama@/bin/ollama serve</string>
</array>
</dict>
</plist>

View file

@ -15,7 +15,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>/nix/store/00000000000000000000000000000000-podman-machine-watchdog-podman-machine-default</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec /nix/store/00000000000000000000000000000000-podman-machine-watchdog-podman-machine-default</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -15,7 +15,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>/nix/store/00000000000000000000000000000000-podman-machine-watchdog-dev-machine</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec /nix/store/00000000000000000000000000000000-podman-machine-watchdog-dev-machine</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -15,9 +15,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@bash-interactive@/bin/bash</string>
<string>/bin/sh</string>
<string>-c</string>
<string>@proton-pass-cli@/bin/pass-cli ssh-agent start --socket-path $(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/proton-pass-agent/socket</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @bash-interactive@/bin/bash -c &apos;@proton-pass-cli@/bin/pass-cli ssh-agent start --socket-path $(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/proton-pass-agent/socket&apos;</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -15,9 +15,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@bash-interactive@/bin/bash</string>
<string>/bin/sh</string>
<string>-c</string>
<string>@proton-pass-cli@/bin/pass-cli ssh-agent start --socket-path $(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/proton-pass-agent/socket --share-id 123456789 --vault-name MySshKeyVault --refresh-interval 7200 --create-new-identities MySshKeyVault</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @bash-interactive@/bin/bash -c &apos;@proton-pass-cli@/bin/pass-cli ssh-agent start --socket-path $(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/proton-pass-agent/socket --share-id 123456789 --vault-name MySshKeyVault --refresh-interval 7200 --create-new-identities MySshKeyVault&apos;</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -15,9 +15,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@bash-interactive@/bin/bash</string>
<string>/bin/sh</string>
<string>-c</string>
<string>@openssh@/bin/ssh-agent -D -a &quot;$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/ssh-agent&quot;</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @bash-interactive@/bin/bash -c &apos;@openssh@/bin/ssh-agent -D -a &quot;$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/ssh-agent&quot;&apos;</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -15,9 +15,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@bash-interactive@/bin/bash</string>
<string>/bin/sh</string>
<string>-c</string>
<string>@openssh@/bin/ssh-agent -D -a &quot;$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/ssh-agent&quot; -P &apos;/usr/lib/libpkcs11.so,/usr/lib/other.so&apos;</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @bash-interactive@/bin/bash -c &apos;@openssh@/bin/ssh-agent -D -a &quot;$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/ssh-agent&quot; -P &apos;\&apos;&apos;/usr/lib/libpkcs11.so,/usr/lib/other.so&apos;\&apos;&apos;&apos;</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -15,9 +15,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@bash-interactive@/bin/bash</string>
<string>/bin/sh</string>
<string>-c</string>
<string>@openssh@/bin/ssh-agent -D -a &quot;$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/ssh-agent&quot; -t 1337</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @bash-interactive@/bin/bash -c &apos;@openssh@/bin/ssh-agent -D -a &quot;$(@getconf-system_cmds@/bin/getconf DARWIN_USER_TEMP_DIR)/ssh-agent&quot; -t 1337&apos;</string>
</array>
<key>RunAtLoad</key>
<true/>

View file

@ -15,7 +15,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@syncthing-wrapper@</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @syncthing-wrapper@</string>
</array>
</dict>
</plist>

View file

@ -27,9 +27,9 @@
<string>Background</string>
<key>ProgramArguments</key>
<array>
<string>@yubikey-agent@/bin/yubikey-agent</string>
<string>-l</string>
<string>/tmp/yubikey-agent.sock</string>
<string>/bin/sh</string>
<string>-c</string>
<string>/bin/wait4path /nix/store &amp;&amp; exec @yubikey-agent@/bin/yubikey-agent -l /tmp/yubikey-agent.sock</string>
</array>
<key>Sockets</key>
<dict>