本文へ移動
cccskills
無料GitHub で公開

convex-cron-jobs

Schedules work in Convex: cron jobs in convex/crons.ts, one off scheduled functions with runAfter and runAt, batching large jobs, and cancelling or inspecting the queue. Use when something needs to run on a timer, later, or in the background, or when a cron is not firing.

インストール方法を見る

含まれるファイル(6)

  • SKILL.md7.7 KB
  • agents/openai.yaml91 B
  • assets/large-logo.png3.2 KB
  • assets/small-logo.svg1.2 KB
  • references/cron-recipes.md8.4 KB
  • references/scheduling-patterns.md9.0 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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:

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.

// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

// Every hour
crons.interval(
  "expire sessions",
  { hours: 1 },
  internal.sessions.expireBatch,
  {},
);

// Every day at 09:00 UTC. Cron expressions are always UTC.
crons.cron("daily digest", "0 9 * * *", internal.digest.send, {});

export default crons;

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):

ExpressionRuns
*/15 * * * *every 15 minutes
0 * * * *every hour at :00
0 0 * * *daily at 00:00 UTC
0 8 * * 1Mondays at 08:00 UTC
0 0 1 * *first of each month
0 9-17 * * 1-5hourly, 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.

// convex/sessions.ts
import { internalMutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

const BATCH = 100;

export const expireBatch = internalMutation({
  args: {},
  returns: v.null(),
  handler: async (ctx) => {
    // Date.now() is fine in a mutation. Never call it in a query.
    const now = Date.now();
    const expired = await ctx.db
      .query("sessions")
      .withIndex("by_expiresAt", (q) => q.lt("expiresAt", now))
      .take(BATCH);

    await Promise.all(expired.map((s) => ctx.db.delete(s._id)));

    // A full batch means more may remain. Chain the next one.
    if (expired.length === BATCH) {
      await ctx.scheduler.runAfter(0, internal.sessions.expireBatch, {});
    }
    return null;
  },
});

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

// Relative: 5 minutes from now
const jobId = await ctx.scheduler.runAfter(
  5 * 60 * 1000,
  internal.reminders.send,
  { taskId: args.taskId },
);

// Absolute: a timestamp you already store (ms since epoch or a Date)
await ctx.scheduler.runAt(trial.endsAt, internal.billing.endTrial, {
  userId: trial.userId,
});
MethodUse 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

MistakeWhy it breaksDo instead
crons.daily(...)deprecated helpercrons.cron("...", "0 0 * * *", ...)
Target is api.tasks.cleanupskips auth, publicly callableregister as internalMutation, use internal.tasks.cleanup
.collect() then loop over thousandshits transaction limitstake(BATCH) and reschedule
.withIndex("by_x").filter(...)filter scans the whole indexput the range in withIndex
Date.now() in an internalQuerybreaks caching and reactivitypass now as an arg from the caller
Cron at "0 9 * * *" for 9am Pacificruns at 9am UTCuse UTC, or run hourly and check local hour
Missing await on runAfterjob may not be scheduledalways await ctx.scheduler.*
Two crons with the same namepush failsunique 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

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Keeps a change scoped to what was asked. Flags extra features, refactors, and 'while we are here' edits, and moves them to a suggestions list instead of the diff. Use when a request is small and the agent is about to expand it, when a PRD grows beyond its problem statement, or when the user says 'just do what I asked'.

日本語の概要は準備中です。原文の説明を表示しています。

waynesutton/builder-skills4072026年9月28日 更新

convex

無料

Routes Convex work to the right builder skill and covers the handful of rules that apply everywhere: validators on every function, withIndex over filter, idempotent mutations, internal functions for scheduling. Use when a task mentions Convex and no more specific skill matches, or as the first stop before picking one.

日本語の概要は準備中です。原文の説明を表示しています。

waynesutton/builder-skills4072026年9月28日 更新

Builds AI agents on the Convex agent component: threads, messages, tools that call queries and mutations, streaming, RAG with vector search, and workflows for multi step jobs. Use when adding a chat assistant, tool calling agent, or retrieval feature to a Convex app.

日本語の概要は準備中です。原文の説明を表示しています。

waynesutton/builder-skills4072026年9月28日 更新

Production patterns for Convex apps and the rules the @convex-dev/eslint-plugin enforces: validators, indexes, idempotent mutations, avoiding OCC conflicts, thin function wrappers, error handling. Use when reviewing Convex code, asking whether a pattern is right, setting up ESLint, or fixing write conflicts and slow queries.

日本語の概要は準備中です。原文の説明を表示しています。

waynesutton/builder-skills4072026年9月28日 更新

Creates reusable Convex components with defineComponent, a clean client wrapper, their own schema, and an npm publish setup. Use when extracting a feature into a package, building something for the Convex component directory, or when a component's functions are not showing up in the parent app.

日本語の概要は準備中です。原文の説明を表示しています。

waynesutton/builder-skills4072026年9月28日 更新

Handles files in Convex: upload URLs, storing blobs from actions, serving with getUrl or an HTTP action, metadata from the _storage table, and deletion. Use when users upload images or documents, when an action fetches a file from a third party, or when a file URL expires unexpectedly.

日本語の概要は準備中です。原文の説明を表示しています。

waynesutton/builder-skills4072026年9月28日 更新

waynesutton のスキルをすべて見る

このスキルの問題を報告する