From 591a744344d6a918ae00cd0429f2fe73a1390703 Mon Sep 17 00:00:00 2001 From: Nick M Date: Mon, 13 Jul 2026 02:12:30 -0500 Subject: [PATCH] docs(miner): add unattended scheduling & failure-alerting guidance Operational guidance (#4840) for running the miner's two scheduled commands (`manage poll`, `discover`) unattended: the shared exit-code contract (0 success / 2 failure) that alerting keys on, the `--no-update-check` / `--json` flags for scheduled runs, worked cron and systemd service+timer examples, and three failure-alerting patterns (cron MAILTO/logger, systemd OnFailure=, and a wrapper script). Closes #4840 --- .../docs/unattended-scheduling.md | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 packages/gittensory-miner/docs/unattended-scheduling.md diff --git a/packages/gittensory-miner/docs/unattended-scheduling.md b/packages/gittensory-miner/docs/unattended-scheduling.md new file mode 100644 index 0000000000..d4392ab425 --- /dev/null +++ b/packages/gittensory-miner/docs/unattended-scheduling.md @@ -0,0 +1,107 @@ +# gittensory-miner — unattended scheduling & failure alerting + +Operational guidance for running the miner's scheduled commands — `manage poll` and `discover` — +unattended on a timer (cron or systemd), and for alerting when a run fails. These are the two commands +most likely to run on a schedule; everything they need is local and they make no interactive prompts. + +> **Scope:** scheduling + failure alerting for `manage poll` / `discover`. For local-state recovery see +> [`operations-runbook.md`](operations-runbook.md); for deployment layout see +> [`../DEPLOYMENT.md`](../DEPLOYMENT.md). + +## The exit-code contract (what to alert on) + +Both commands follow the same convention, so any scheduler can detect a failed run from the exit code: + +| Exit code | Meaning | +| --- | --- | +| `0` | Success — the command completed. | +| `2` | Failure — invalid arguments, or the run hit an error (network / API / local state). **Alert on this.** | + +For scheduled runs, two flags matter: + +- `--no-update-check` (or `GITTENSORY_MINER_NO_UPDATE_CHECK=1`) — skip the npm-registry version nudge so + an unattended run never depends on / prints it. +- `--json` — machine-parseable stdout, so an alert handler can attach the structured output. + +## cron + +```cron +# crontab env applies to every job below. +MAILTO=you@example.com +GITTENSORY_MINER_NO_UPDATE_CHECK=1 + +# Poll a tracked PR every 10 minutes. The `||` branch fires on any non-zero exit: it logs the failing +# code to syslog AND re-raises it with `exit "$status"`, so the failure stays visible to exit-status +# monitoring instead of being masked by logger's own success. +*/10 * * * * /usr/local/bin/gittensory-miner manage poll acme/widgets 42 --json || { status=$?; logger -t gittensory-miner "manage poll failed (exit $status)"; exit "$status"; } + +# Discover + enqueue candidate work hourly. +0 * * * * /usr/local/bin/gittensory-miner discover --search "label:good-first-issue" --json || { status=$?; logger -t gittensory-miner "discover failed (exit $status)"; exit "$status"; } +``` + +Two cron facts to get right here: + +- **`MAILTO` mails *output*, not exit status.** cron emails whatever a job writes to stdout/stderr to + `MAILTO` — it does not send a message "because" the exit code was non-zero. A job that fails *silently* + (non-zero exit, no output) produces no mail, so don't rely on `MAILTO` alone as the failure signal. +- **A bare `|| logger …` hides the failure.** `logger` succeeds (exit 0), so `cmd || logger …` makes the + whole cron job exit 0 — any exit-status-based monitoring then sees success. Capture the code first + (`status=$?`) and re-raise it (`exit "$status"`) as shown, so the real failing code survives. + +## systemd (service + timer) + +A `oneshot` service plus a timer is the more observable option: `systemctl status` / `journalctl` +capture each run, and `OnFailure=` is a first-class alerting hook. + +`gittensory-miner-discover.service`: +```ini +[Unit] +Description=gittensory-miner discover +OnFailure=gittensory-miner-alert@%n.service + +[Service] +Type=oneshot +Environment=GITTENSORY_MINER_NO_UPDATE_CHECK=1 +# A non-zero exit (2) marks the unit failed and triggers OnFailure=. +ExecStart=/usr/local/bin/gittensory-miner discover --search "label:good-first-issue" --json +``` + +`gittensory-miner-discover.timer`: +```ini +[Unit] +Description=Run gittensory-miner discover hourly + +[Timer] +OnCalendar=hourly +Persistent=true + +[Install] +WantedBy=timers.target +``` + +Enable with `systemctl enable --now gittensory-miner-discover.timer`. + +## Alerting on failure + +Every option keys on the same exit-code contract (`2` = failure): + +- **cron:** append `|| { status=$?; ; exit "$status"; }` (as in the cron example above) — + capture `$?` before the alert command runs and re-raise it, so the failure isn't masked. Substitute + `logger` with a webhook `curl`, a PagerDuty/Slack CLI, etc. (`MAILTO` still mails any output, but is not + a reliable signal for a silent failure — see the cron note above.) +- **systemd:** `OnFailure=gittensory-miner-alert@%n.service` runs a templated alert unit on any non-zero + exit. A minimal alert unit: + ```ini + # gittensory-miner-alert@.service + [Service] + Type=oneshot + ExecStart=/usr/local/bin/notify-failure "gittensory-miner unit %i failed" + ``` +- **wrapper script:** for any scheduler, wrap the command and preserve its exit code: + ```sh + #!/bin/sh + gittensory-miner "$@" || { status=$?; notify-failure "gittensory-miner $* exited $status"; exit "$status"; } + ``` + +Keep `--json` on scheduled runs so the alert handler can forward the structured output; the +human-readable form is for interactive use.