Skip to content
← Guides

Your job started and never finished. Nothing errored. Here's how to catch it.

A hung run is the quietest failure there is: no crash, no exit code, no error email. The process just sits there, or the agent waits forever on a tool call. To catch it, something outside the job has to expect a finish by a deadline.

Short answer

Expect a finish check-in by a deadline, not just a start. Did It Run? does this for cron jobs, scripts, n8n, Zapier, Make, GitHub Actions and AI agents: the job pings its watch's URL when it finishes, and if no finish arrives by the expected time plus your grace window, you get a missed alert. A run that calls /start and then hangs never sends that finish, so it's flagged missed at the deadline, and the watch shows when it last started. Start free (3 watches, no credit card) or follow the cron recipe.

Why hung runs don't alert

  • Nothing exits, so nothing reports. Error emails, exit-code checks and "on failure" hooks all wait for the process to end. A hang never ends.
  • The network call that never returns. An API or database call with no timeout can wait indefinitely on a stalled connection.
  • The AI agent that keeps "working". A loop of retries, a tool call waiting on a page that never loads, or a step waiting for input nobody will give. The scheduler shows the run as in progress.
  • The next run can't start. Some schedulers skip or queue a run while the previous one is still going, so one hang can silently stop every run after it.
  • Platform limits are generous. A GitHub Actions job runs for up to 360 minutes by default before GitHub cancels it (`timeout-minutes`, workflow syntax docs). That's six hours of "running" before anything changes.

Step 1: check in when the job finishes, not when it starts

A start ping alone proves the job began. Only a finish ping proves it got to the end. Put the success ping as the last step of the job, after the real work.

In Did It Run?:

  • https://diditrun.app/p/<your-token>/start records that a run began. It shows as Last start on the watch and as a started event in the history. It does not count as the run arriving.
  • https://diditrun.app/p/<your-token> is the finish (success) check-in.
  • https://diditrun.app/p/<your-token>/fail reports a failure. The watch turns failed and you're alerted straight away (once when it turns failed, not again for every failed run).

Step 2: set the deadline past the normal run time

Create the watch with the time the job should normally be done by, and a grace window for slow days. For a job that starts at 2:00am and usually takes 20 minutes, expect it "every day by 2:30am" with 30 minutes of grace. Grace can be anything from 0 minutes to 7 days (the default is 15 minutes).

If the job hangs, no finish arrives, and you get a missed alert ("No run arrived by …") at the deadline. The watch's Last start tells you it did start, so you know you're looking at a hang rather than a job that never launched.

Did It Run? doesn't have a separate maximum-run-time alert. The deadline is what catches a hang, so set it as tight as your slowest normal run allows.

Step 3: turn a hang into a failure with a timeout

A deadline tells you a run didn't finish. A timeout inside the job also stops the hung process and lets you report why. On Linux, GNU timeout runs a command with a time limit and exits with status 124 if the command timed out (GNU coreutils manual):

bash
#!/usr/bin/env bash
PING="https://diditrun.app/p/<your-token>"
curl -fsS -m 10 --retry 3 "$PING/start" > /dev/null

timeout --kill-after=60 45m /opt/jobs/nightly.sh
status=$?

if [ "$status" -eq 0 ]; then
  curl -fsS -m 10 --retry 3 "$PING" > /dev/null
elif [ "$status" -eq 124 ]; then
  curl -fsS -m 10 --retry 3 -X POST --data-binary "nightly.sh timed out after 45 minutes" "$PING/fail" > /dev/null
else
  curl -fsS -m 10 --retry 3 -X POST --data-binary "nightly.sh exited with status $status" "$PING/fail" > /dev/null
fi

--kill-after=60 sends KILL if the job ignores the first signal for 60 seconds; in that case timeout exits with 137, which the last branch reports as a failure. Note the -m 10 on every curl: the check-in itself should never be the thing that hangs.

In GitHub Actions, set timeout-minutes on the job to something close to its normal run time instead of relying on the 360-minute default, and keep the success ping as the last step. If the job is cancelled, the ping never runs and the watch flags the run missed. See the GitHub Actions guide.

AI agents and no-code workflows

  • AI agents: make the check-in the agent's final action ("when you've finished the digest, request this URL"), or wrap the agent's command in timeout as above. An agent stuck in a loop never reaches the final step, so the deadline catches it. See dead man's switch for AI agents.
  • n8n, Zapier, Make: put the HTTP step that pings the watch at the very end of the workflow. HTTP Request in n8n, HTTP → Make a request in Make, Webhooks by Zapier → POST in Zapier (a Premium app, so it needs a paid Zapier plan). See the n8n, Zapier and Make recipes.

Missed, failed, late: what you'll see for a hang

  • Missed: no finish check-in by the expected time plus grace. A hang without a timeout ends up here.
  • Failed: the job called /fail, for example from the timeout branch above.
  • Late (optional): the expected time passed and you're still inside the grace window. Turn this on if you want an early heads-up on a run that's running long.

You get one alert when a watch goes missed or failed, not one per run, and a recovery notice when a good run comes in.

FAQ

Does a /start ping count as the job running?

No. /start records the start time (shown as Last start on the watch) and a started event in the history, but only a success or /fail check-in counts as the run arriving. A run that starts and never finishes is flagged missed at the deadline.

Can I get an alert after a run has been going for X minutes?

There isn't a separate maximum-run-time alert. Set the watch's expected time and grace window so the deadline falls just after your slowest normal run, and use a timeout in the job so a hang becomes a /fail with a reason.

Is hang detection on the free plan?

Yes. Missed, late, failed and empty detection is included on every plan, including Free (3 watches, email alerts).

Can I check in by email instead of a URL?

Not yet. Email check-ins are coming soon. Today every check-in is an HTTP request to the watch's ping URL, with /start and /fail variants.

Does Did It Run? kill or restart the hung job?

No. Did It Run? never runs your code or connects to your accounts. It only judges the check-ins your job sends, so stopping a hung process is up to a timeout in the job or your scheduler.

Know the next time it doesn't run

Know when your AI agents and automations didn't run. Free for 3 watches.

Start free