Troubleshooting
Symptoms, in rough order of how often they come up.
The monitor never appeared
Nothing in Monitoring → Monitors after running the job.
- The process exited before the check-in was sent. By far the most common cause in short CLI scripts and serverless handlers. The SDK sends in the background; if the process ends first the request never leaves. Call
\Sentry\flush()orawait Sentry.flush(2000)before exiting. - The DSN is wrong or the key is revoked. Ingest answers
401 invalid dsn, and most SDKs swallow that silently. Check the key's Last used column in the project's Keys section: if it is not moving, nothing is arriving. - The DSN points somewhere else. An app migrated from Sentry often has both
SENTRY_DSNand a framework-specific variable set, and the framework one wins. Print the DSN the SDK resolved at boot. - You are looking at the wrong organization. The list is scoped to the organization you are currently in.
To isolate whether the problem is the SDK or the transport, send a check-in with plain curl. If curl produces a monitor and the SDK does not, the problem is in your process, not in ingest.
Schedule and Next expected are both -
The monitor exists, check-ins are landing, and nothing will ever be detected.
The check-in carried no monitor_config. Every SDK makes it optional, so this is easy to do:
// No config: a monitor with no schedule.
\Sentry\captureCheckIn(slug: 'nightly-reports', status: CheckInStatus::ok());Send the config on at least the first check-in of each run. There is no way to add a schedule from the panel.
Schedule is set but Next expected is empty
The config arrived and could not be turned into a next time.
unit: "year". Not supported. Use a crontab expression.- An interval of zero or a negative value.
- An unparseable cron expression. Five fields, standard syntax. Non-standard extensions such as
@dailyare not accepted.
Fix the config and run the job once. The value is recomputed on every check-in.
Missed alerts firing when the job did run
The job runs, and Parsemend says it did not.
- The margin is too tight for the job's real start-time variance. A job queued behind four others at midnight does not start at midnight. Widen
checkin_margin. - Interval drift. With an interval schedule the next expectation is measured from the last check-in, so a job that consistently starts slightly late walks its own window out of alignment. Switch to a crontab schedule.
- Timezone mismatch. If the schedule is in local time and the monitor is in UTC, expect a false alarm twice a year at the daylight-saving boundary, and a constant offset otherwise.
- The check-in was rate limited. Check-ins share the project's ingest budget with errors. A crash loop can consume it and drop check-ins with it. Missed alerts that coincide exactly with an error spike are this.
- The terminal check-in is being dropped, not the first one. Check the monitor's check-in history: rows stuck at
in_progressmean the start is arriving and the finish is not.
Everything is timeout
Runs start and are never seen to finish.
- The check-in id is not being passed back. Without it, the terminal check-in creates a second row instead of closing the first, leaving the
in_progressrow to age out. In the history this looks like two rows per run. - A backgrounded Laravel scheduled task with the wrong cache store.
->runInBackground()stores the check-in id in cache to survive the process boundary. On thearraydriver it does not survive. max_runtimeis shorter than the job. Look at the durations in the check-in history and set the budget to roughly twice the worst one.- The job really is hanging. Worth ruling in before ruling out.
The status is stuck on unknown
The monitor only ever receives in_progress, or a status string it does not recognise. Only ok and error move the monitor out of unknown. Check that your success path actually sends its check-in, including when the job returns early.
Two monitors for one job
A duplicate slug. Usually one of:
- An auto-derived Laravel slug.
sentryMonitor()without an explicit slug derives one from the command string, so renaming the command creates a new monitor. Always pass an explicit slug. - The same job running from two places with slightly different slugs (
nightly-reportsandnightly_reports). - A slug that includes an environment or hostname. If it does that deliberately, expect one monitor per host and size your alert rules for it.
The old monitor cannot be deleted from the panel. It will sit at missed. Ask an operator to remove the row if it bothers you.
Duration looks 1000x wrong
duration is in seconds. A 15-minute job showing 900000 ms in the history is sending milliseconds. Divide by 1000.
The issue will not stay resolved
Working as designed. Resolving a monitor issue by hand does not fix the job, and the next failed slot regresses the same issue. Fix the job, or disable the monitor at the source by removing the check-in from the code.
An issue for a monitor nobody recognises
Usually a slug from a decommissioned job. Nothing removes a monitor when the code that fed it is deleted, so it sits there missing its slot forever. Remove the alert rule's interest in it, or ask an operator to delete the row.
Worth checking the opposite reading first: a monitor with no recent check-ins that nobody remembers deploying can also be a job that is still scheduled somewhere everyone forgot about.
Still stuck
Collect the exact envelope your client is sending, along with the HTTP status ingest returned, before asking. The response body says which of the failure modes above you are in, and most reports arrive without it.
