OpenSEO Cron Jobs: Complete Guide to Scheduled Tasks and Their Purposes
OpenSEO runs four scheduled cron jobs that handle OAuth token cleanup, rank tracking, audit reconciliation, and polling safeguards, all dispatched through Cloudflare Workers via wrangler.jsonc configuration.
This guide breaks down every scheduled task in the every-app/open-seo repository, including exact cron expressions, source file locations, and how the server dispatches each job. Whether you're debugging stuck audits or extending the scheduler, you'll find the precise implementation details here.
What Are OpenSEO's Scheduled Cron Jobs?
OpenSEO uses Cloudflare Workers cron triggers to run background maintenance and data processing tasks. The system defines schedules in wrangler.jsonc and handles execution branching in src/server.ts. Each job serves a distinct operational purpose, from security hygiene to real-time ranking data collection.
| Cron Expression | Schedule | Job Name | Primary Purpose |
|---|---|---|---|
17 3 * * * |
Daily at 3:17 AM UTC | MCP OAuth Purge | Remove stale OAuth credentials and revoke expired tokens |
*/5 * * * * |
Every 5 minutes | Scheduled Rank Checks | Poll rank-tracking queue and trigger checks per organization |
* * * * * |
Every minute | Audit Watchdog | Reconcile stale audits that became stuck during processing |
*/15 * * * * |
Every 15 minutes | Rank-Check Guard | Kill overlapping poll windows to prevent runaway loops |
MCP OAuth Purge Cron (3:17 AM Daily)
The MCP OAuth purge job handles security maintenance by cleaning up expired authentication state.
Source Location and Implementation
In src/server.ts at lines 191-200, the server checks the incoming cron trigger and dispatches to the OAuth cleanup handler:
// src/server.ts (lines 191-200)
if (controller.cron === MCP_OAUTH_PURGE_CRON) {
// Purge stale OAuth credentials
// Revoke expired access tokens from storage
// Clean up abandoned authorization flows
}
Why 3:17 AM?
The 3:17 AM UTC timing spreads load across Cloudflare's infrastructure by avoiding common midnight/2:00 AM windows. This reduces contention with other Workers users while ensuring daily cleanup completes before peak usage hours.
Security Impact
- Prevents token replay attacks by proactively removing expired credentials
- Reduces storage costs by pruning abandoned authorization flows
- Maintains compliance with OAuth 2.0 token lifetime requirements
Scheduled Rank Checks Cron (Every 5 Minutes)
The rank-tracking polling loop drives OpenSEO's core SEO monitoring feature, checking search positions for tracked keywords across configured organizations.
Source Location
Implemented in src/server/features/rank-tracking/services/scheduledRankChecks.ts:
// src/server/features/rank-tracking/services/scheduledRankChecks.ts
export async function scheduledRankChecks(controller: Controller, env: Env) {
while (true) {
// Claim work for the current configuration
const work = await claimRankCheckWork(env);
if (!work) break; // No work available
// Enforce per-organization plan limits
const limits = await checkPlanLimits(work.organizationId, env);
if (limits.exceeded) {
await deferWork(work, limits.resetTime);
continue;
}
// Schedule next check eagerly
await triggerRankCheck(work, env);
await scheduleNextCheck(work, env);
}
}
Key Design Patterns
- Work-claiming pattern: Prevents duplicate processing across concurrent Workers instances
- Plan limit enforcement: Checks organization subscription tiers before executing expensive rank queries
- Eager scheduling: Queues the next check immediately to maintain cadence even with variable processing time
Audit Watchdog Cron (Every Minute)
The audit reconciliation job acts as a safety net for the site audit pipeline, detecting and recovering from stuck or orphaned audit processes.
Source Location
Implemented in src/server/features/audit/services/auditReconciler.ts:
// src/server/features/audit/services/auditReconciler.ts
export async function reconcileStaleAudits(controller: Controller, env: Env) {
// Find audits stuck in 'running' state beyond timeout threshold
const staleAudits = await findStaleAudits(env, STALE_THRESHOLD_MINUTES);
for (const audit of staleAudits) {
// Determine failure mode from worker logs
const diagnosis = await diagnoseStall(audit, env);
// Recover or fail gracefully based on diagnosis
if (diagnosis.recoverable) {
await resumeAudit(audit, env);
} else {
await markFailed(audit, diagnosis.error, env);
}
}
}
Stall Detection Logic
The watchdog identifies stuck audits by:
- Timestamp comparison: Audits running longer than
STALE_THRESHOLD_MINUTES(typically 30 minutes) - Heartbeat absence: No progress updates written to the audit log stream
- Worker death detection: Associated Cloudflare Worker invocation no longer active
Rank-Check Guard Cron (Every 15 Minutes)
Though not a primary business task, this sub-hourly guard prevents a failure mode in the 5-minute polling loop. Referenced in code comments within scheduledRankChecks.ts, it ensures:
// Defensive timeout within the 5-minute poll loop
const POLL_WINDOW_MS = 15 * 60 * 1000; // 15 minutes
setTimeout(() => controller.abort(), POLL_WINDOW_MS);
Overlap Prevention
- Kills long-running rank-check iterations that exceed the 15-minute safety bound
- Prevents cascading queue congestion if an external API degrades
- Ensures fresh Worker instances spin up for each cron invocation
Cron Configuration in wrangler.jsonc
All schedules are declared in the Cloudflare Workers configuration:
// wrangler.jsonc
{
"triggers": {
"crons": [
"17 3 * * *", // MCP_OAUTH_PURGE_CRON
"*/5 * * * *", // SCHEDULED_RANK_CHECKS_CRON
"* * * * *", // AUDIT_RECONCILER_CRON
"*/15 * * * *" // RANK_CHECK_GUARD_CRON (implicit)
]
}
}
The server maps these expressions to constants in src/server.ts for dispatch:
// src/server.ts
const MCP_OAUTH_PURGE_CRON = "17 3 * * *";
const SCHEDULED_RANK_CHECKS_CRON = "*/5 * * * *";
const AUDIT_RECONCILER_CRON = "* * * * *";
Debugging Cron Jobs in Development
To trace cron execution locally:
# Simulate specific cron trigger
npx wrangler dev --test-scheduled
# Trigger OAuth purge manually
curl "http://localhost:8787/__scheduled?cron=17+3+*+*+*"
Monitoring Production Execution
| Metric | Source | Alert Threshold |
|---|---|---|
| Cron invocation count | Cloudflare Workers analytics | < 95% of expected runs per hour |
| Duration p99 | Workers tail logs | > 50 seconds for any 1-minute cron |
| Error rate | controller.error captures |
> 0.1% of invocations |
Summary
- Four cron jobs power OpenSEO's background operations, configured in
wrangler.jsoncand dispatched fromsrc/server.ts - MCP OAuth purge (
17 3 * * *) cleans authentication state daily at 3:17 AM UTC - Scheduled rank checks (
*/5 * * * *) poll and execute keyword position monitoring every 5 minutes with plan-limit enforcement - Audit watchdog (
* * * * *) reconciles stuck audits every minute to maintain pipeline health - 15-minute guard prevents runaway rank-check loops from congesting the queue
Frequently Asked Questions
How do I add a new cron job to OpenSEO?
Add your cron expression to wrangler.jsonc under triggers.crons, then add a corresponding constant and handler branch in src/server.ts. Test locally with wrangler dev --test-scheduled before deploying.
What happens if a cron job fails?
Failed cron invocations surface in Cloudflare Workers tail logs with controller.error populated. The audit watchdog and rank-check services implement idempotent work-claiming, so transient failures resume safely on the next cron tick without duplicate processing.
Can I change the OAuth purge time from 3:17 AM?
Yes—modify the expression in wrangler.jsonc and update the MCP_OAUTH_PURGE_CRON constant in src/server.ts. Cloudflare recommends avoiding exact hour boundaries; the 17-minute offset exists specifically to distribute load.
Why does the rank checker need to run every 5 minutes rather than on-demand?
The polling design accommodates hundreds of organizations with varying check frequencies and plan limits. The 5-minute loop batches rate-limited external API calls efficiently while respecting per-organization concurrency constraints that pure event-driven architecture cannot guarantee.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →