Troubleshooting
Start with the job’s run history (dashboard, GET /api/v1/jobs/{id}/runs, or the get_run MCP tool): most questions are answered by a run’s status, start lag and attempts. For server-level problems, check /readyz and the logs, which carry job_id and run_id on every relevant line.
A job didn’t fire
| Check | How | Fix |
|---|---|---|
| Is it active? | Job state | paused → resume. disabled → read disabled_reason, fix, re-enable. completed → the schedule ran out (count, until, one-off). |
| When is it due? | next_runs on the job, or cronpad schedule next | Check the timezone. 0 9 * * * in UTC is 14:30 in India. |
| Was it skipped? | Runs with status skipped | Misfire policy skip, or overlap_policy: skip_if_running with a run still in progress. |
| Is the server scheduling? | /readyz → dispatcher, queue | standby: another process holds the dispatcher lock (two servers on one data directory). queue: false: the queue file can’t be read or written; check disk space and permissions on the data volume. |
Runs start late
- Look at
start_lag_mson the run, andcronpad.queue.oldest_due_ageandcronpad.dispatcher.backpressureon the server. - Lots of jobs due at the same second (e.g. midnight) with slow endpoints use up the worker pool. Increase
workers.count, or spread schedules (5 0 * * *instead of0 0 * * *). - After a restart or deploy, runs due during the downtime start late by design; see Misfires.
cronpad.reconciler.repairedincreasing means queue entries were lost and restored. That shouldn’t happen in normal operation; check the logs for queue errors (cronpad.queue.errors) and whether something is deleting or replacing the queue file.
A webhook fails
Open the run and read each attempt.
| Attempt shows | Likely cause |
|---|---|
401 / 403 | Wrong or expired token. Update the secret. |
404 | Wrong URL or method. |
4xx, then the run fails without retries | 4xx (other than 408/429) is fatal by default. Add the code to retry_on if it’s actually transient. |
timeout | Endpoint slower than the job’s timeout. Raise it, or make the endpoint respond quickly and do the work asynchronously. |
network: … connection refused / DNS errors | Endpoint down or unreachable from cronpad. |
blocked: private address | On cronpad Cloud (or hosted mode), the URL resolves to a private IP. Use a public endpoint. |
secret NAME not found | The job references a deleted secret. |
Send test on the job reproduces one request with full details, without recording a run.
My endpoint received the same run twice
That’s at-least-once delivery doing its job after a crash, a timeout, or a lease loss. Deduplicate on Idempotency-Key / webhook-id (the run ID, identical across attempts). See Delivery guarantees. Cronpad-Attempt tells you whether it was a retry or a redelivery of the same attempt.
Signature verification fails
- Verify against the raw body bytes, before any JSON parsing or re-serialization.
- Use the full secret including decoding the part after
whsec_from base64. - Signed content is
{webhook-id}.{webhook-timestamp}.{body}. - Check your server clock: timestamps older than 5 minutes are rejected by the reference code.
- After a rotation, both secrets are valid for 24 hours; make sure you deployed the new one.
A job was disabled
It had too many consecutive failed runs (20 by default). disabled_reason says how many; the last runs say why. Fix the cause, then Re-enable. See Auto-disable.
The server won’t start
| Log message | Fix |
|---|---|
config: … (a list of problems) | Each line names a setting and what’s wrong with it. |
no sign-in method configured | Configure Google or GitHub, or auth.dev_login = true for local testing only. |
database is locked at startup | Another cronpad process is using the same files. Run exactly one. |
the [redis] section is ignored (warning) | Left over from a Redis-based release. Remove the [redis] section or CRONPAD_REDIS__* variables; the queue is now a SQLite file. |
queue: … | The queue file couldn’t be opened or created. Check that the data directory is writable. A corrupt file is moved aside as *-queue.db.corrupt-<time> and recreated automatically. |
secrets: cannot decrypt | secrets.encryption_key differs from the key the data was encrypted with. |
Still stuck
Open an issue with the cronpad version (cronpad version), the X-Request-ID or run_id, and the relevant log lines (they’re already redacted).