convex-cron-jobs

Scheduled function patterns for background tasks including interval scheduling, cron expressions, job monitoring, retry strategies, and best practices for long-running tasks

By waynesutton · 2,674 installs

npx skills add waynesutton/convexskills --skill convex-cron-jobs

Source repository · Upstream listing

Convex cron jobs and scheduling Recurring work lives in convex/crons.ts . One off work is scheduled from inside a function with ctx.scheduler . Both must target internal. functions, never api. . When to reach for this Something needs to run every N minutes or at a fixed time of day A mutation needs to kick off follow up work after it commits A job touches more rows than one mutation should handle A cron shows in the dashboard but never runs, or runs at the wrong hour A pending job needs to be cancelled, debounced, or inspected Deeper material lives in two reference files: [references/scheduling patterns.md](references/scheduling patterns.md): open when you need retry with backoff, cancelling a job, reading scheduled functions , debouncing, or local time zones. [references/cron recipes.md](references/cron recipes.md): open for short copyable jobs: daily digest email, session expiry, stats rollup, external API sync. crons.ts skeleton One file, one default export. crons.interval for "every N", crons.cron for calendar times. crons.daily , crons.hourly , and crons.weekly are deprecated helpers. Do not use them. Rules for every entry: Names are unique within the file. The dashboard lists jobs by this name. Import internal from ./ generated/api , even when the target is defined in crons.ts . Args are static and must satisfy the target function's args validator. Interval units are { seconds } , { minutes } , or { hours } . Cron expression quick reference (minute hour day of month month day of week): Expression Runs /15 every 15 minutes 0 every hour at :00 0 0 daily at 00:00 UTC 0 8 1 Mondays at 08:00 UTC 0 0 1 first of each month 0 9 17 1 5 hourly, 09:00 to 17:00 UTC, weekdays Targets are internal functions Public functions expect a client, an auth identity, and untrusted input. Cron and scheduler calls have none of that. A public target skips the auth checks you wrote and exposes the job to anyone who can reach the deployment. Register targets with internalMutation , internalAction , or internalQuery . One batched job A mutation is one transaction with read and write limits. Deleting fifty thousand rows in a loop hits them. Take a fixed slice, reschedule yourself with runAfter(0, ...) , and let the chain finish on its own. This shape works because each delete removes the row from the index range. If the job updates rows without moving them out of the range, use a pagination cursor instead. See the reference file. runAfter vs runAt Method Use for runAfter(delayMs, fn, args) retries, follow ups, "in ten minutes" runAt(timestamp, fn, args) trial ends, send dates, anything with a stored time Both return an Id<" scheduled functions" . Store it on a document if you may need to cancel. Two behaviors to remember: Scheduling inside a mutation is transactional. If the mutation throws, nothing is scheduled. Scheduling inside an action happens right away, even if the action fails later. Scheduled mutations run exactly once. Scheduled actions may fail without retry, so add retry logic to actions or use a retry component. Seeing runs in the dashboard Schedules, Cron Jobs tab: every entry from crons.ts , with last run and next run. Schedules, Scheduled Functions tab: pending runAfter and runAt jobs. Logs, filtered by function name: each execution, its duration, and any thrown error. From the CLI: npx convex logs streams the same log lines. From code: ctx.db.system.get(jobId) returns the job document with state.kind set to pending , inProgress , success , failed , or canceled . When a cron is not firing 1. The file is exactly convex/crons.ts and ends with export default crons . 2. npx convex dev is running and the last push succeeded. Cron changes only apply on push. 3. The target is internal. and the args match its validator. A mismatch fails at push time. 4. The expression is UTC. Convert your local hour before comparing. 5. Check Logs for a thrown error. A job that throws every run looks like a job that never runs. Common mistakes Mistake Why it breaks Do instead crons.daily(...) deprecated helper crons.cron("...", "0 0 ", ...) Target is api.tasks.cleanup skips auth, publicly callable register as internalMutation , use internal.tasks.cleanup .collect() then loop over thousands hits transaction limits take(BATCH) and reschedule .withIndex("by x").filter(...) filter scans the whole index put the range in withIndex Date.now() in an internalQuery breaks caching and reactivity pass now as an arg from the caller Cron at "0 9 " for 9am Pacific runs at 9am UTC use UTC, or run hourly and check local hour Missing await on runAfter job may not be scheduled always await ctx.scheduler. Two crons with the same name push fails unique names per file Checklist [ ] convex/crons.ts uses only crons.interval and crons.cron , ends with export default crons [ ] Every cron and scheduler target is internal. [ ] Every target has args and returns validators [ ] Jobs that touch many rows take a batch and reschedule with runAfter(0, ...) [ ] Range conditions live in withIndex , not .filter [ ] No Date.now() inside queries [ ] Every ctx.scheduler. call is awaited [ ] Cron hours are written in UTC [ ] Job ids are stored on documents when cancel or debounce is needed [ ] Ran npx convex dev and saw the job listed under Schedules Docs https://docs.convex.dev/llms.txt https://docs.convex.dev/scheduling/cron jobs https://docs.convex.dev/scheduling/scheduled functions https://docs.convex.dev/database/advanced/system tables