convex-cron-jobs — скилл для Claude Code: что делает и как установить
Настраивает периодические задачи в convex/crons.ts и отложенный запуск через ctx.scheduler. Помогает разбить мутации на пачки и отладить фоновые очереди.
npx skills add waynesutton/builder-skills --skill convex-cron-jobs -a claude-codeЧто делает convex-cron-jobs
Скилл учит агента работать со встроенным планировщиком Convex. Он задаёт расписания в файле convex/crons.ts через crons.interval или стандартные cron-выражения в UTC. Однократные фоновые задачи планируются прямо из кода через ctx.scheduler.runAfter и ctx.scheduler.runAt.
Все вызовы обязаны указывать на функции internal.*, а не на публичные api.*. Публичный вызов обходит проверки авторизации и открывает доступ к задаче снаружи. Для больших объёмов данных скилл строит цепочки. Мутация обрабатывает порцию в 100 записей через take(100) и вызывает runAfter(0, ...) для следующей пачки. Это спасает от превышения лимитов транзакции.
Планирование внутри мутаций транзакционно. Если мутация завершилась ошибкой, задача не встанет в очередь. С действиями action всё иначе, поэтому для них скилл напоминает настраивать логику повторов.
Какие задачи решает
- настроить периодический запуск функций в convex/crons.ts
- отложить выполнение фоновой задачи через runAfter или runAt
- разбить обработку строк на цепочки пачек по 100 элементов
- найти причину сбоя cron-задачи в логах Convex
Примеры запросов агенту
- Настрой cron в Convex для отправки ежедневного дайджеста каждый понедельник в 08:00 UTC
- Помоги разбить удаление устаревших сессий на пачки через ctx.scheduler.runAfter
- Почему cron в convex/crons.ts отображается в дашборде, но не запускается?
Как установить convex-cron-jobs
1Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-cron-jobs -a claude-code2Вручную: положить файлы
~/.claude/skills/convex-cron-jobs/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/convex-cron-jobs/
1Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-cron-jobs -a codex2Вручную: положить файлы
.agents/skills/convex-cron-jobs/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-cron-jobs -a cursor2Вручную: положить файлы
.cursor/skills/convex-cron-jobs/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install waynesutton/builder-skills convex-cron-jobs2Вручную: положить файлы
.github/skills/convex-cron-jobs/SKILL.md1Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-cron-jobs -a gemini-cli2Командой платформы
gemini skills install https://github.com/waynesutton/builder-skills --path skills/convex-cron-jobsВручную: положить файлы
.devin/skills/convex-cron-jobs/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add waynesutton/builder-skills --skill convex-cron-jobs -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Из того же набора14
Похожие
Рядом в теме «Бэкенд и API»
Всё по теме «Бэкенд и API» →Вопросы о convex-cron-jobs
Как установить convex-cron-jobs в Claude Code?
Выполните команду npx skills add waynesutton/builder-skills --skill convex-cron-jobs -a claude-code.
Что делает convex-cron-jobs?
Скилл обучает агента правилам и паттернам планировщика Convex. Он охватывает регулярные задачи в convex/crons.ts, отложенные вызовы через ctx.scheduler и батчинг мутаций.
Почему задачи должны вызывать только internal-функции?
Публичные функции рассчитаны на внешних клиентов и пропускают проверку прав при вызове планировщиком. Регистрация через internalMutation или internalAction защищает логику от прямого вызова извне.
Исходный текст
SKILL.md · Apache-2.0
---
name: convex-cron-jobs
description: 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.
---
# 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.
```typescript
// 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):
| 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.
```typescript
// 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
```typescript
// 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,
});
```
| 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
© waynesutton · Apache-2.0 · waynesutton/builder-skills