Schedule nightly maintenance
Schedule a maintenance workflow and resume the same run after a quota pause.
What this example covers
Section titled “What this example covers”- Cron schedulesPublish one job per night, in your time zone.
- Job queues and workersRun each job in a separate worker process.
- Durable runsSave each finished task in a checkpoint.
- Quota pausesPause on a usage limit instead of failing.
- Fallback agentsHand the work to a second agent at the limit.
- Typed responsesValidate the agent’s final report.
Run a scheduler and a worker as two separate processes sharing .outpost/jobs.sqlite. The scheduler publishes maintenance jobs and the worker runs them. Save the two scripts beside the configuration from Installation.
Schedule the nights
Section titled “Schedule the nights”Both schedules give the same night the same runId, such as deps-2026-09-29. The 07:00 job resumes that run if a usage limit paused it.
Run the workflow
Section titled “Run the workflow”Define the report, agent and update task.
Publish the report and process checkpointed jobs in worker.ts.
Run the script
Section titled “Run the script”Run each command in its own terminal or service. Ctrl+C stops either one cleanly.
Understand the steps
Section titled “Understand the steps”Claude Code can report when its limit resets; Codex never does. A run stopped by Codex alone therefore waits for the 07:00 job. With the fallback, the task pauses only when both agents hit their limit, and the reset is known only if both report one.
Finished tasks always come from the checkpoint. resume: "retry-incomplete" authorizes the rest to run again: a task interrupted by a worker stop, or one that failed during the night.
If you stop the worker during a run, its job returns to the queue after the 30-second lease. The restarted worker claims it and continues from the interrupted task.
Adapt the example
Section titled “Adapt the example”Another chore
Section titled “Another chore”Change the brief and the report schema: fix lint warnings, remove dead code, update a changelog. Keep one dated branch per run so each morning has its own review.
Approve before merging
Section titled “Approve before merging”Add a defineApprovalTask() after update, then a task that merges the branch. The job completes paused and lists the gate in pauses. Submit the decision with workflow.start() and the runId and version from the job value.
Use Redis
Section titled “Use Redis”Replace createSqliteTaskQueue() with createBullMQTaskQueue() from Redis and BullMQ to run workers on several machines. Give each worker a unique worker name.
Schedule from CI
Section titled “Schedule from CI”Without a long-running scheduler, a scheduled CI job can call nightly(runId).start() with the same checkpoint and onQuota options. Store checkpoints in S3 or R2 so the next CI run resumes a paused one; see Run in CI.
Limits
Section titled “Limits”- A worker killed without a clean stop keeps ownership of the checkpoint. The next job for that run fails until you clear ownership with
recoverWorkflowCheckpoint()(Durable runs). - A resumed task continues the captured conversation of a single agent. With a fallback agent, it restarts from the first candidate and the original brief, on the same branch.
- One worker runs jobs one at a time, so the 07:00 job waits behind a run still in progress. With several workers, that job fails while the run is still executing.
API: runSchedules · createCronSchedule · runQueueWorker · defineWorkflowJob · WorkflowQuotaPolicy · createFallbackAgent · defineJsonResponse.