I have written a shell script that I want to run every time I log on to my Macbook, and I'm trying to execute it via launchd.
My script is not a persistent daemon, it's supposed to write some text to a file and then be done until my next login.
This is the plist I created for it, which plutil -lint pronounces OK:
Label
com.example.test
Program
/Users/Tom/Desktop/tomtest.sh
KeepAlive
OnDemand
LaunchOnlyOnce
StandardOutPath
/Users/Tom/Desktop/tomtest.log
StandardErrorPath
/Users/Tom/Desktop/tomtest.log
Debug
I copied it to ~/Library/LaunchAgents/com.example.test.plist and did sudo chown to give it to root:wheel. Shortly after doing so, I got a system notification telling me about this new service; I did not capture the text to share here, but it seemed normal (i.e. a good sign). And, when I open System Settings > General > Login Items & Extensions, I see my script:

Here is the entirety of the shell script tomtest.sh:
#!/bin/sh
echo "I ran" > /Users/Tom/Desktop/tomtest.log
exit 0
Despite logging out and in again, the log file doesn't even exist. So I manually created it (touch) and retried, but still no dice.
When I run the script manually from the CLI, it writes text to the log file as expected.
I've been trying to debug/fix this using launchctl, but I keep getting this error:
~ $ launchctl bootstrap gui/501 com.example.test
Bootstrap failed: 5: Input/output error
Try re-running the command as root for richer errors.
~ $ sudo launchctl bootstrap gui/501 com.example.test
Password:
Bootstrap failed: 5: Input/output error
Here is the relevant output of launchctl print gui/501:
disabled services = {
"com.apple.ManagedClientAgent.enrollagent" => disabled
"com.example.OLDtest" => enabled
"com.apple.Siri.agent" => disabled
"com.apple.FolderActionsDispatcher" => enabled
"com.apple.appleseed.seedusaged.postinstall" => disabled
"com.apple.ScriptMenuApp" => disabled
}
My com.example entry is there, but (1) it is listed, confusingly, as "enabled" within the "disabled" block, and (2) it has an old name. The old name is from a previous version of the plist file that had a typo in the name, but that version of the service also never worked. And I have never seen the new version appear, despite having a different filename and Label. com.example does not appear anywhere else in the ~2000 lines of output.
When I run launchctl load ~/Library/LaunchAgents/com.example.test, I get no output and no error, but the log file is still blank.
Why am I getting "Bootstrap failed: 5: Input/output error" from such a simple program?
In case I've got an X/Y problem here: my real goal is not to write to a log file; I have written a zsh script that generates new desktop wallpaper for me every day, and the script I really want to run will generate the PNG file and then use osascript to actually change the desktop wallpaper. It works when I run it from the CLI.
So, my goal is not to launch a persistent service such as a DB server. I want macOS to run my script once every time I log in, which could be several times per day, and each time I expect my script to take about 2 seconds and then exit 0. My research suggests that the launchd system is the best tool for this, but I am open to other suggestions that are equally invisible -- I will not accept the "Open at Login" because it opens the Terminal app to execute my script.
I am the only user of this machine.
M4 Macbook Air running macOS Sequoia 15.5