General Computing
mac terminal python cron launchd
Updated Thu, 21 Jul 2022 01:00:47 GMT

Python Script/Unix Executable Runs in Terminal, Fails as Cron/Launchd job


I'm trying to configure launchd to trigger a python script/unix executable (i.e. python script with shebang line). When I load the .plist file (below), launchctl shows a status of 127 meaning, "The specified service did not ship with the operating system". However when I copy and paste the value I've entered for 'program' in the .plist file into the mac terminal it runs fine.

I've redirected stdout/stderr to the terminal (via the .plist) and it returns the message,

$ env: python3: No such file or directory

If I replace the value of Program in the plist to a simple 'hello world' esque batch script it works fine.

Why does the python program (urlwatch) run fine in terminal but return an error when called via launchd? How do I fix it?

Plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" \
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>local.careersWatch3</string>
        <key>Program</key>
        <string>/Users/justinmichael/Documents/urlwatch-master/urlwatch</string>
        <key>RunAtLoad</key>
        <true/>
        <key>StandardOutPath</key>
        <string>/dev/ttys000</string>
        <key>StandardErrorPath</key>
        <string>/dev/ttys000</string>
    </dict>
</plist>

I eventually want to run the script at set times of the day, but for now I'm using RunAtLoad = true for testing purposes until I can get it to work.

Loading into launchd and output:

$ launchctl load  ~/Library/LaunchAgents/local.careerswatch3.plist
$ env: python3: No such file or directory

Call to check status of agent and output:

$ launchctl list | grep local.careersWatch3
-   127 local.careersWatch3

Looking up the meaning of code '127' in terminal:

$ launchctl error 127
127: The specified service did not ship with the operating system



Solution

The problem was with the environment variables, specifically that the $PATH is different for jobs run by cron vs programs called by me in terminal as a logged in user. Calling echo $PATH in a cron job and checking if it includes the directory of the python interpreter can help to confirm this is the problem.

Two solutions:

1) Quick and Dirty

Find where the Python interpreter is installed and change the shebang line at the top of the unix executable/python script to call it directly. i.e

#!/usr/bin/env python3

becomes

#!/usr/local/bin/python3

Here, it doesn't matter if the python interpreter is on the path or not because it's location is given explicitly. The downside is that its location is now hard-coded and if you move the script to a different computer the script may not work, neither in cron nor when run in the terminal, if python has been installed in a different location.

2) Less Quick, Less Dirty

Write a shell script that adds the location of the python interpreter to the path if it isn't already there (as per this SuperUser question) and then calls the python script. This way the script hasn't been changed and won't be accidentally broken by moving it to a computer where python is installed in a different directory.

#!/bin/bash
# directory python is found in
dir="usr/local/bin"
#add to path if not there
if [ -d "$dir" ] && [[ ":$PATH:" != *":$dir:"* ]]; then
        PATH="${PATH:+"$PATH:"}$dir"
fi
#Run program
/path/to/program/program_name

Be sure to make the script executable via chmod +x /path/to/script/script.sh