What happens
Every function that edits the crontab uses this shape:
crontab -l 2>/dev/null | grep -vF "$nudge_bin" > "$tmp" || true
crontab "$tmp"
crontab -l exits non-zero in two very different situations:
- there is no crontab yet — normal, and the intended case for
|| true;
- the crontab could not be read — spool permissions, a broken cron install, a filesystem problem.
Both produce an empty $tmp, which is then installed — replacing the user's entire crontab with nothing.
Reproduction
With a shim whose -l fails for a reason other than "no crontab":
cat > bin/crontab <<'SH'
#!/usr/bin/env bash
if [[ "$1" == "-l" ]]; then echo "crontab: cannot read: I/O error" >&2; exit 1; fi
echo "INSTALLED CONTENT:"; cat "$1"
SH
PATH="bin:$PATH" bash -c 'source env.sh; source services/cron.sh; cron_remove'
# INSTALLED CONTENT:
# <- empty. every entry the user had is gone
Honest scoping
I could not construct a realistic trigger on a healthy system — on a working box crontab -l fails only when there is no crontab, which is exactly the case the code handles correctly. This is hardening, not an observed failure.
It is worth doing anyway because the data at risk is not refocus's: it is every unrelated cron job the user has. And the code path runs often — enable, disable, reset, import, and setup.sh install all rewrite the crontab.
Suggested fix
Capture output and status separately and only treat an empty read as legitimate:
existing=$(crontab -l 2>/dev/null) || existing=""
# proceed only when the failure really was "no crontab" (empty output);
# otherwise refuse to write and say so, rather than installing an empty file
Location
services/cron.sh — cron_install, cron_remove, cron_checkin_install, cron_checkin_remove. Same shape in setup.sh:88-91.
What happens
Every function that edits the crontab uses this shape:
crontab -lexits non-zero in two very different situations:|| true;Both produce an empty
$tmp, which is then installed — replacing the user's entire crontab with nothing.Reproduction
With a shim whose
-lfails for a reason other than "no crontab":Honest scoping
I could not construct a realistic trigger on a healthy system — on a working box
crontab -lfails only when there is no crontab, which is exactly the case the code handles correctly. This is hardening, not an observed failure.It is worth doing anyway because the data at risk is not refocus's: it is every unrelated cron job the user has. And the code path runs often —
enable,disable,reset,import, andsetup.sh installall rewrite the crontab.Suggested fix
Capture output and status separately and only treat an empty read as legitimate:
Location
services/cron.sh—cron_install,cron_remove,cron_checkin_install,cron_checkin_remove. Same shape insetup.sh:88-91.