Script Works Manually but Not in Cron
Short answer
Cron does not run your script inside the same environment as your interactive terminal. It typically uses a different PATH, a different working directory, a minimal non-login shell, and sometimes a different user — and it doesn’t load your shell profile (.bashrc, .zshrc, .profile) by default. A script that depends on any of these implicitly will behave differently, or fail, under cron.
The most common differences
PATH. Cron’s defaultPATHis short (often just/usr/bin:/bin). Tools installed via Homebrew,nvm,pyenv,rbenv, or a custom install location may simply not be found.- Working directory. Cron does not
cdinto your project directory. Relative paths that work from your terminal will resolve from$HOMEor wherever cron starts. - Shell. Cron typically uses
/bin/sh, not your interactive/bin/bashor/bin/zsh. Bash-specific syntax can silently fail or error out. - User and permissions. A cron job may run as a different user (or the same user but without an interactive session’s group memberships and umask), affecting file access.
- Environment variables. Anything exported in your shell profile — API keys,
NODE_ENV, custom variables — is not present unless you set it explicitly in the crontab or script. - Virtualenv / pyenv / nvm not loaded. These tools work by modifying your interactive shell’s
PATHand environment on login. Cron never triggers that, sopython3ornodemay resolve to the wrong version or not at all. - Relative file paths. Any
open("data.csv")or./config.yamlreference resolves relative to cron’s working directory, not your project root. - Mounted drives or network paths. Drives mounted in your desktop session (via
autofs, a login script, or a GUI file manager) may not be mounted in cron’s context at all.
Quick diagnostic checklist
Add this as the first lines of the script (or a wrapper) to capture what cron actually sees, then compare against your interactive shell:
pwd
whoami
env | sort
command -v python3
command -v mysqldump
Run the same four commands manually in your terminal and diff the output. The differences are usually the root cause.
Safe crontab pattern
SHELL=/usr/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
* * * * * cd /absolute/project && ./script.sh >> logs/cron.log 2>&1
Setting SHELL and an explicit PATH at the top of the crontab, always cd-ing into an absolute project path before running, and redirecting both stdout and stderr to a log file removes most of the ambiguity above in one pattern.
Why this becomes a silent failure
A script that partially runs under cron’s restricted environment doesn’t always crash cleanly. It may create an output file, write a header row, then fail before writing real data — exiting 0 because the failure was swallowed by a missing command, a caught exception, or a masked pipe status. A heartbeat ping or bare exit-code check will not catch this; the process technically completed.
Continuous prevention
The environment mismatch is a one-time debugging problem, but validating that the job actually produced correct output should be continuous. Checking exit code alone tells you the process ran under whatever environment cron gave it — it does not tell you the output is correct.
Where Night Watchman fits
Night Watchman monitors the artifact a cron job produces — not just whether the process exited cleanly. If a PATH or environment mismatch causes a script to silently write an empty or malformed file, Night Watchman checks freshness, size, and shape against history and escalates an evidence dossier citing exactly what it found, rather than trusting the exit code.
FAQ
Why is cron’s PATH different from my shell’s PATH?
Cron starts jobs with a minimal, non-interactive environment and does not source your shell profile (.bashrc, .zshrc, .profile) by default, so anything that profile normally sets up — including PATH extensions from version managers — is missing.
Should I use absolute paths in cron?
Yes. Absolute paths for both the working directory (cd /absolute/project) and any file references remove the ambiguity of “relative to what?” that cron’s working directory otherwise introduces.
How do I know if the output is actually valid, not just that the script ran?
Check the produced artifact directly: file size, row or record count, expected schema/header, freshness, and whether it’s plausible compared with prior runs — not just the script’s exit code.
Related pages
- Backup job succeeded but the file is empty
- What is a silent cron failure?
- Exit code 0 is not a health check
Try Night Watchman
Install from the public repository:
curl -fsSL https://raw.githubusercontent.com/ssddee-dev/code-batman/main/install.sh | bash
cd ~/night-watchman
.venv/bin/python -m watchman.setup