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

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.

(cherry picked from commit eec72f1278)
This commit is contained in:
Wael Nasreddine
2026-01-27 22:00:47 -08:00
committed by GitHub
parent 75ed713570
commit 366d78c285
23 changed files with 105 additions and 62 deletions
+25 -1
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
@@ -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.
'';
}
+3 -3
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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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>
@@ -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/>
@@ -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/>
@@ -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>
@@ -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/>
@@ -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>
@@ -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/>
@@ -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/>
@@ -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>
@@ -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>
@@ -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/>
@@ -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/>
@@ -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>
@@ -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>