Skip to content

Railway Crons (Back Office) #16

Railway Crons (Back Office)

Railway Crons (Back Office) #16

Workflow file for this run

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