Railway Crons (Back Office) #16
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Railway Crons (Back Office) | |
| # Revives the 18 scheduled jobs that previously ran on Vercel Cron, which is dead | |
| # after the Vercel -> Railway cutover. Each schedule below maps to one or more | |
| # /api/cron/* endpoints (GET, protected by `Authorization: Bearer ${CRON_SECRET}`). | |
| # Without this, the Back Office (recovery-scan, opportunity-scout, morning-brief, ...) | |
| # never runs, so no recovery opportunities are generated. | |
| # | |
| # Requires repo secret CRON_SECRET (must equal the CRON_SECRET env on the Railway | |
| # service). Optional repo variable DECK_CRON_BASE_URL overrides the default base. | |
| # | |
| # NOTE: GitHub-scheduled workflows run only from the default branch and can be delayed | |
| # under load (fine for daily/hourly business jobs). Manual run via workflow_dispatch. | |
| on: | |
| schedule: | |
| - cron: "30 11 * * 1-5" # morning-brief (weekdays) | |
| - cron: "0 10 * * *" # recovery-scan | |
| - cron: "0 */6 * * *" # follow-ups + follow-ups/execute + war-room-sweep | |
| - cron: "0 8 * * *" # opportunity-scout + monitor | |
| - cron: "0 15 * * *" # pilot-engagement | |
| - cron: "0 12 * * *" # trial-expiry | |
| - cron: "0 10 * * 1" # weekly-digest | |
| - cron: "0 11 * * 1" # dissent-digest | |
| - cron: "0 6 * * *" # self-heal | |
| - cron: "0 3 * * 0" # purge-documents | |
| - cron: "0 7 * * 1" # webhook-cleanup | |
| - cron: "0 3 * * 6" # cache-purge | |
| - cron: "0 4 * * 0" # memory-maintenance | |
| - cron: "0 6 * * 0" # audit-retention | |
| - cron: "0 2 * * 0" # gc-day | |
| workflow_dispatch: | |
| inputs: | |
| endpoint: | |
| description: 'Single cron endpoint to run (e.g. recovery-scan), or "all"' | |
| required: false | |
| default: "all" | |
| concurrency: | |
| group: railway-crons-${{ github.event.schedule || github.event.inputs.endpoint }} | |
| cancel-in-progress: false | |
| jobs: | |
| dispatch: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Call due cron endpoint(s) | |
| env: | |
| BASE: ${{ vars.DECK_CRON_BASE_URL || 'https://deck.relaylaunch.com' }} | |
| CRON_SECRET: ${{ secrets.CRON_SECRET }} | |
| TRIGGER_SCHEDULE: ${{ github.event.schedule }} | |
| MANUAL_ENDPOINT: ${{ github.event.inputs.endpoint }} | |
| run: | | |
| set -uo pipefail | |
| if [ -z "${CRON_SECRET:-}" ]; then | |
| echo "::error::CRON_SECRET repo secret is not set"; exit 1 | |
| fi | |
| declare -a eps=() | |
| if [ -n "${TRIGGER_SCHEDULE:-}" ]; then | |
| case "$TRIGGER_SCHEDULE" in | |
| "30 11 * * 1-5") eps=(morning-brief) ;; | |
| "0 10 * * *") eps=(recovery-scan) ;; | |
| "0 */6 * * *") eps=(follow-ups follow-ups/execute war-room-sweep) ;; | |
| "0 8 * * *") eps=(opportunity-scout monitor) ;; | |
| "0 15 * * *") eps=(pilot-engagement) ;; | |
| "0 12 * * *") eps=(trial-expiry) ;; | |
| "0 10 * * 1") eps=(weekly-digest) ;; | |
| "0 11 * * 1") eps=(dissent-digest) ;; | |
| "0 6 * * *") eps=(self-heal) ;; | |
| "0 3 * * 0") eps=(purge-documents) ;; | |
| "0 7 * * 1") eps=(webhook-cleanup) ;; | |
| "0 3 * * 6") eps=(cache-purge) ;; | |
| "0 4 * * 0") eps=(memory-maintenance) ;; | |
| "0 6 * * 0") eps=(audit-retention) ;; | |
| "0 2 * * 0") eps=(gc-day) ;; | |
| *) echo "Unrecognized schedule: $TRIGGER_SCHEDULE"; exit 0 ;; | |
| esac | |
| elif [ "${MANUAL_ENDPOINT:-all}" = "all" ]; then | |
| eps=(morning-brief recovery-scan follow-ups follow-ups/execute opportunity-scout \ | |
| pilot-engagement trial-expiry weekly-digest dissent-digest war-room-sweep \ | |
| monitor self-heal purge-documents webhook-cleanup cache-purge \ | |
| memory-maintenance audit-retention gc-day) | |
| else | |
| eps=("${MANUAL_ENDPOINT}") | |
| fi | |
| fail=0 | |
| for ep in "${eps[@]}"; do | |
| echo "→ /api/cron/$ep" | |
| code=$(curl -sS -o /tmp/body.txt -w '%{http_code}' \ | |
| -H "Authorization: Bearer ${CRON_SECRET}" \ | |
| --max-time 300 "${BASE}/api/cron/${ep}" || echo "000") | |
| echo " $ep → HTTP $code" | |
| if [ "$code" != "200" ]; then | |
| echo "::warning::cron $ep returned $code"; head -c 300 /tmp/body.txt || true; echo | |
| fail=1 | |
| fi | |
| done | |
| exit $fail |