Skip to content

Runs and failure reasons

Every time Delay sends a request, it records a run: what was sent, what came back, how long it took and, if it failed, why. Crons call their runs executions. Each job attempt has exactly one run, and every retry is a new job with its own run.

Where to find runs

  • Crons: open the cron from Cron jobs. Recent executions shows the last 10 runs. View all opens the Execution history, 20 runs per page. Click a row to see its details.
  • Jobs: the Jobs list shows each job’s status, HTTP code, failure reason and duration. Open a job to see its request and response.

For a step-by-step walkthrough, see Debug a failed run.

What a run records

FieldWhat it holds
Started atWhen Delay began the run
Completed at (crons), Executed at (jobs)When the response arrived or the run failed
DurationMilliseconds from the start of the run to the response, including the DNS lookup and connecting
Response statusThe HTTP status code, if a response arrived
Response headersThe headers your endpoint returned
Response bodyWhat your endpoint returned, up to 65,535 bytes
Request headersThe headers you configured
Request bodyFor crons, the payload string as you wrote it. For jobs, the JSON payload.
Error messageWhat went wrong, for example HTTP 503 error or Assertion failed: ...
Failure reasonOne of the values in Failure reasons

A few details worth knowing:

  • Long bodies are cut. Bodies over 65,535 bytes are stored cut short with ... at the end. The Execution history page shows the first 2,000 characters of each body. Assertions always check the full body, not the stored copy.
  • Delay’s own headers aren’t listed. Request headers show only the headers you set. Content-Type, User-Agent and the signature headers are sent but not recorded. The full list is in What your endpoint receives.
  • A cron’s request body is shown before wrapping. If your cron payload is plain text, it is sent as {"body": "..."} but recorded as the text you wrote (see Cron schedules).
  • Runs are deleted with their cron, job or queue. Deleting a cron deletes its execution history. Purging a dead-lettered job, or its automatic cleanup after the queue’s Dead letter retention, deletes its run.

Run status

A cron execution is in one of four states: Pending, Processing, Completed or Failed.

Completed means a response arrived. It doesn’t mean success: a cron that gets a 500 back is Completed, shown with a red status code, and has the failure reason http_server_error. Failed means there was no usable response (timeout, connection or DNS problems), an assertion failed, or the monthly limit was reached.

A run counts as successful only when a 2xx response arrived and no failure reason was set. For job statuses and retries, see Jobs.

Failure reasons

Every failed run gets one of these reasons. The dashboard shows the text in the Label column, for example in the Jobs list and on the Alerts page.

ReasonLabelWhat happenedWhat to do
timeoutTimeoutNo complete response within the request timeout, or the connection couldn’t be opened within 10 secondsMake the endpoint answer faster (reply first, then do the work), or raise the timeout (up to 300 seconds)
connection_errorConnection errorThe connection was refused, reset or unreachableCheck the server is up, listening on the right port, and that its firewall accepts outside traffic
dns_errorDNS errorThe hostname couldn’t be resolvedCheck the domain spelling and its DNS records
ssl_errorSSL/TLS errorThe TLS handshake failedCheck the certificate is valid, not expired and has its full chain. The server must support TLS 1.2 or newer.
http_client_errorClient error (4xx)Your endpoint returned a 4xx statusCheck the URL, method, auth headers and payload. The response body usually says what’s wrong.
http_server_errorServer error (5xx)Your endpoint returned 500 or higherCheck your server logs for the error at that time
invalid_urlInvalid URLThe URL has no host, or uses a scheme other than http or httpsFix the URL
assertion_failedAssertion failedA 2xx response arrived but a success assertion didn’t passThe error message names the assertion. Compare it with the response body.
limit_exceededMonthly limit reachedYour team reached its monthly request limit, so no request was sentUpgrade, or wait for the next month. See How usage is counted.
unknownUnknown errorAnything else (see below)Read the error message

The most common causes of unknown:

  • A redirect. Delay doesn’t follow redirects, so a 301, 302, 307 or 308 is a failure. Use the final URL (check https, www and trailing slashes).
  • “URL resolves to a blocked IP address”. The hostname points to a private or reserved address (such as 10.x, 192.168.x or 127.0.0.1). Delay only calls public addresses.
  • “Cannot resolve host”. The hostname has no public IPv4 address. Check its DNS records. Hosts that only have an IPv6 address can’t be called.

Job attempts that fail for any of these reasons are retried according to their queue’s settings, except limit_exceeded (see Queues). Crons never retry.

How usage is counted

Each run counts as one request toward your team’s monthly quota:

  • every cron execution, scheduled or started with Run now;
  • every job attempt, so a job that succeeds on its third try counts three times;
  • failed runs, and runs blocked by the limit.

Callbacks and alert notifications don’t count. The count is per team and resets at the start of each calendar month, in UTC.

PlanRequests per monthWarning at
Free1,000800 (80%)
Starter20,00018,000 (90%)
Pro75,00067,500 (90%)
Business500,000450,000 (90%)
Enterprise5,000,0004,500,000 (90%)

You can see this month’s count on the Usage tab.

At the warning level, Delay adds one “Limit warning” alert per month to the Alerts page. It is shown in the app only, not sent by email, Slack or Discord.

At the limit, every new run fails at once with limit_exceeded, and no request is sent:

  • Jobs are not retried and don’t go to the dead letter queue. No callback is sent.
  • Crons stay on their schedule and record a failed run each time they come due.
  • The API still accepts new jobs and crons with a 201. The limit is checked when a run starts, not when you create it.

The count is refreshed about once a minute, so a busy team can go slightly over its limit before runs are blocked.

Next steps