Jobs keep the application healthy when no user is actively clicking a button. They remove old temporary records, complete account lifecycle cleanup, and protect internal maintenance routes from public access.
The app currently has two scheduled Vercel cron jobs. Other cleanup work also exists, but those flows run as part of chat, project, storage, or stream behavior instead of as separate cron routes.
Scheduled Jobs
The scheduled jobs are registered in vercel.json.
| Job | Route | Schedule | Purpose |
|---|---|---|---|
| Delete stale deactivated accounts | /api/internal/jobs/delete-stale-deactivated-accounts | 0 0 1 * * | Deletes accounts that have been deactivated for more than 30 days. |
| Delete stale empty chats | /api/internal/jobs/delete-stale-empty-chats | 0 */12 * * * | Deletes empty chats older than 15 minutes. |
Delete Stale Deactivated Accounts
This job runs once per month.
It calculates a cutoff time 30 days in the past, then deletes users whose deactivatedAt value is older than that cutoff.
The purpose is to keep account deactivation as a real lifecycle instead of only hiding a user from the UI. Users can reactivate during the deactivation window, but old deactivated accounts are eventually removed from the database.
Delete Stale Empty Chats
This job runs every 12 hours.
It calculates a cutoff time 15 minutes in the past, then deletes chats created before that cutoff when they have no messages.
The purpose is to clean up abandoned chat shells. A user can start a new chat, refresh, navigate away, or leave before sending a message. Without this job, those empty records would build up over time and make chat data noisier than it needs to be.
Internal Job Protection
Scheduled job routes are internal routes, not public product endpoints. Each route checks the Authorization header against CRON_SECRET before doing any work.
If the secret is missing or invalid, the route returns 401 Unauthorized. The routes also return X-Robots-Tag headers so crawlers do not index or cache internal maintenance responses.
Set CRON_SECRET in every deployed environment that runs scheduled jobs.
CRON_SECRET="replace-with-a-long-random-secret"Related Cleanup Behavior
The broader background job feature list also includes cleanup behavior that is not configured as a Vercel cron entry.
| Cleanup | Where it happens | Purpose |
|---|---|---|
| Active stream cleanup | AI chat stream lifecycle | Clears active stream state when a stream finishes, stops, or errors. |
| Stale active stream state management | AI chat runtime state | Keeps stream state from getting stuck after refreshes or interrupted sessions. |
| Redis active stream tracking | Redis runtime state | Coordinates resumable chat streams and fast temporary state. |
| Blob cleanup after chat deletion | Chat deletion flow | Deletes stored attachment blobs after a chat is deleted. |
| Blob cleanup after project deletion | Project deletion flow | Deletes stored project and chat blobs after a project is deleted. |
| Message source cleanup | Chat persistence flow | Removes stale file source rows when message attachments change. |
These are still maintenance concerns, but they are tied to the product flow that created the data. The cron jobs are reserved for periodic cleanup that should happen even when no user is currently active.