Skip to content
Metric VaultHelp Center
Open app

Background jobs console

The four background jobs, what their status columns really mean, how they are actually scheduled, and what "Run due now" does and does not do.

Last updated 2026-08-06

Summary#

The Job Console shows the four recurring jobs that run without a user pressing anything: scheduled reports, rank alerts, the competitor monitor and tier-2 AI visibility tracking. For each it reports the last run, the next run, how many items it manages and whether any are due. An owner can trigger a job immediately. That trigger is not a forced re-run, and understanding why is most of what this page is for.

Purpose#

When a customer says their weekly report never arrived, there are only three possible causes: the schedule is not enabled, the job has not run, or the job ran and the send failed. This screen settles the first two in a glance, without a database query, and its Run due now button settles the third by making the job run while you watch.

Requirements#

  • An active admin identity to read the screen. See Getting admin access.
  • The owner role to trigger a job.
  • MONITOR_DB bound. Every figure comes from D1.
  • For the tier-2 job to do anything, OPENAI_API_KEY set on the environment.

Permissions#

ActionRole neededEndpoint
Read job statusadmin/api/admin/jobs
Trigger a job nowowner/api/admin/jobs/run

A non-owner gets 403 {"error":"Only an owner can run jobs."} and does not see the button.

Admin console → Operations → Jobs

The screen's own title is Job Console.

How scheduling actually works#

This matters more than the screen does, because the console cannot tell you whether the scheduler is alive.

The worker has a scheduled() handler, but the site runs on Cloudflare Pages, and Pages never calls scheduled(). Cron triggers are a Workers feature. The jobs are therefore driven from outside, by an HTTP call:

  • POST /api/cron/run runs whatever is due. Every job self-throttles in the database, so calling it often is safe and idempotent.
  • A small standalone Cloudflare Worker in cron-worker/ holds a real cron trigger and calls that endpoint every 15 minutes. It is the primary scheduler and is deployed separately from the site.
  • A GitHub Actions workflow calls the same endpoint hourly as a backup.

Both callers must present MV_INTERNAL_SECRET. The check fails closed: if the secret is unset on the Pages environment, /api/cron/run returns 503 and nothing runs at all. The same secret must match in three places: the Pages project, the cron worker's secrets, and the GitHub repository secrets.

Important

Important: If every job on this screen shows a stale last run and a growing due count, suspect the scheduler or the secret before suspecting the jobs. Nothing on this screen reports scheduler health.

Reading the table#

The table Scheduled & background jobs has six columns: Job, Status, Last run, Next run, Items, Actions.

ColumnMeaning
StatusGrey Disabled when the job is switched off by its environment, amber Due now when at least one item is due, green Scheduled otherwise
Last runA relative time, or the literal unknown when there is nothing recorded. It is never faked
Next runin Xm, in Xh, in Xd, or when the job has no computable next time
ItemsHow many enabled things the job manages, not how many are due

Two of those columns need a caveat, and the screen states it in its own explainer: only tier-2 AI visibility records a true job-level last run. For the other three, Last run is derived from the most recently processed item, so an empty job with no items shows unknown even though the scheduler is calling it on time. Rank alerts and the competitor monitor have no computable next run at all, because each item carries its own interval, so their Next run is always .

Screenshot
The Job Console showing the four jobs with their status chips, last run, next run and item counts, and a Run due now button on each row.

The four jobs#

JobLabel on screenWhat it doesCadenceLast run comes fromEnabled when
schedulesScheduled reportsSends each customer's scheduled report when its next-run time passesPer scheduleThe most recent report that firedAlways
rank_alertsRank alertsRe-checks each enabled rank alert and notifies on movementEvery 24 hours per alertThe most recently checked alertAlways
monitorCompetitor monitorRe-fetches each monitored URL on its own interval and reports changesPer URL, from its own interval settingThe most recently checked URLAlways
tier2AI visibility (tier-2)Measures tracked brands across AI models, up to 5 per runRoughly every 22 hoursA real job-level timestampOPENAI_API_KEY is set

Each row carries a note under the label. For rank alerts and the monitor it reads either <n> alert(s) due now. and <n> URL(s) due now., or the steady state Enabled alerts; each re-checked every 24h. and Enabled URLs; each on its own interval. For scheduled reports it reads Enabled schedules; last run = the last one that fired. For tier-2 it reads Tracked brands (up to 5 measured per run, ~every 22h)., or OPENAI_API_KEY not set — this job is a no-op. when the key is missing.

Step-by-Step Guide#

Check whether a job is running#

  1. Select Jobs in the Operations group.
  2. Read Last run for the job in question. A time within the expected cadence means the scheduler is reaching the worker.
  3. Read Status. Due now for a sustained period means items are queuing up and the job is not being called, or is failing before it processes them.
  4. Read Items. A count of zero explains an unknown last run without implying a fault.

Run a job now#

  1. Press Run due now on the job's row. The button label becomes Starting….
  2. The page shows Triggered <job> — it processes only items that are due. Refreshing status… and reloads the table after four seconds.
  3. On failure the message is Error: <message>.

The endpoint returns immediately with {ok:true, job, triggered:true} and the job itself runs in the background, so a successful trigger tells you the job started, not that it finished. Re-read Last run after the refresh, and check the Run Log or the error log for what it did.

Warning

Warning: Run due now is not a forced re-run. It calls exactly the same function the scheduler calls, and every job still processes only items that are due. Tier-2 keeps its 22-hour throttle, so pressing the button an hour after a tier-2 run does nothing measurable. If you need a specific item processed ahead of schedule, change that item's own next-run or last-checked time; the console cannot do that for you.

Every trigger is audited as jobs.run with the job key as the target. See Audit log.

What the endpoint accepts#

POST /api/admin/jobs/run takes { token, job } where job is one of monitor, schedules, rank_alerts or tier2. Anything else returns 400 {"error":"Unknown job."}. There is no rate limit, and no lock: pressing the button twice starts the job twice, though the due-item throttling means the second pass usually finds nothing left to do.

Troubleshooting#

SymptomLikely causeFix
Every job shows an old Last run and a rising due countThe external scheduler is not reaching the worker, or MV_INTERNAL_SECRET is unset or mismatchedCheck the cron worker's deploy and the secret in all three places; /api/cron/run returns 503 when the secret is unset
Last run reads unknown on a healthy jobThe job has no processed items yet, so there is nothing to derive a time fromCheck Items. Zero items is not a fault
Next run is for rank alerts and the monitorNeither job has a single next time; each item carries its own intervalExpected. Use Due now and the item count instead
Tier-2 shows DisabledOPENAI_API_KEY is not set on the environmentSet the key and redeploy. Until then the job is a no-op
Run due now succeeded but nothing changedNo items were due, or tier-2's 22-hour throttle has not elapsedConfirm with the item note in the row
No Run due now button anywhereYour role is adminAn owner triggers the job
A customer's scheduled report never arrived, but the job ranThe schedule may be disabled, or the send failed after the job processed itCheck the schedule itself and the error log. See Error log and resolution
The whole screen fails to loadMONITOR_DB is not boundRestore the binding and redeploy

FAQs#

Can I pause a job? Not from this screen. Three of the four are always enabled and tier-2 is enabled by the presence of an environment variable. Pausing individual work means disabling the individual schedules, alerts or monitored URLs.

Can I see what a job did on its last pass? Not here. The Job Console reports state, not output. Per-run detail lives in the Run Log, and failures group into the error log.

Does triggering a job cost anything? It costs whatever the due work costs in provider and model calls. Tier-2 measures up to five brands per run. Because only due items are processed, pressing the button does not multiply that cost.

Why is the scheduler a separate deploy? Because Pages cannot run cron. The cron worker is a real Cloudflare Worker with a cron trigger whose only job is to call /api/cron/run. Deploying the site does not deploy it.

Do jobs notify anyone when they fail? Not directly. Failures surface as grouped issues in the error log and, for failed automation steps, on the Workflow Failures screen. Nothing pages a human.

See also

Was this article helpful?