deadman: a dead-man's switch for n8n
Your automation ran every night for four months. Then the scheduler stopped queueing it, or the credential behind it lapsed, and it produced nothing for thirty hours, and nothing on your instance told you so. An error workflow only fires when something ran and failed. Nothing ran, so there was no error to catch. deadman is a small outside observer that reads the absence.
It reads your n8n instance's own execution history over its public REST API and answers one question per workflow: did anything start when it should have? One program file, Python 3.11 or newer, standard library only. No packages, no daemon, no database, nothing installed inside n8n. It runs from cron and exits. It is read only: every call it makes is a GET, and it never starts, stops, retries or edits an execution.
The three things it can tell you apart
| class | what happened | why n8n's own error workflow cannot see it |
|---|---|---|
| SILENCE | a workflow due on a schedule produced no execution inside
every + grace | nothing ran, so there is no failed execution to alert on. This is the thirty-hours-nobody-noticed case. |
| ERROR | a new failed execution appeared (n8n statuses
error, crashed) | it can see this one. deadman reports it without you wiring an error workflow into every board, and it dedupes so one failure is reported once. |
| PROBE-FAULT | the n8n API did not answer, or answered 401 or 403, or answered garbage | this is not about your workflows at all. It is what a rotated API key or a lapsed Cloud trial looks like. |
The order is deliberate. PROBE-FAULT is decided first and it suspends every workflow verdict. If deadman cannot read your instance it says "could not read n8n" and exits 2. It does not report forty seven workflows as dead because its own key expired. Reporting someone else's outage as your workflows' death is the most expensive lie a monitoring tool can tell.
Proof, and how to repeat every number on this page
Measured on Python 3.12.3 with pytest 9.1.1, on 2026-11-14, from the copy served at /browse/ on this site.
| check | the exact command | what it printed |
|---|---|---|
| the rule suite, under pytest | python3 -m pytest test_deadman.py -q |
51 passed, 0 failed |
| the same suite through its own runner, which counts assertions rather than tests | python3 test_deadman.py |
deadman rules: 51 passed, 0 failed, 270 assertions |
| the one-command check: compiles, runs the suite, runs the shipped demo, checks idempotence, checks a bad config | ./selfcheck.sh |
RESULT PASS (6/6 steps) |
On a machine whose login session carries a display, a bare test run is refused by this project's own guard so a test can never reach someone's screen. The run above is the same command inside that box.
What the shipped demo prints, with no n8n instance and no API key
$ python3 deadman.py --config config.example.toml --simulate demo_fixture.json \
--now 2026-11-13T12:00:00Z --state /tmp/dm_state.json
2026-11-13T12:00:00Z ALERT error:PLACEHOLDER_WORKFLOW_ID 1 new failed execution(s): 4412 (statuses error)
2026-11-13T12:00:00Z ALERT silence:PLACEHOLDER_SECOND_WORKFLOW_ID no execution in 1200s (expected every 900s + 300s grace); last seen: id 4401 at 2026-11-13T09:00:00Z (status success)
Two failure classes, one line each, exit code 1. Run it twice and the second run sends nothing
new: an alert fires once when it opens, re-fires only after cooldown_s, and clears with
exactly one RECOVERED notice. State is one small JSON file written atomically, so killing
the process mid-run cannot corrupt it.
Exit codes, which are the whole contract under a monitor
| code | meaning |
|---|---|
| 0 | every workflow due started, or the probe fault is already known and reported |
| 1 | at least one alert is open (silence or a new failed execution) |
| 2 | probe fault: the instance could not be read, or the config is unusable. Workflow verdicts are suspended, not guessed |
The source, in the open
- deadman.py (54,754 bytes) is the whole program, one file. It is
served as a download rather than a rendered page, because a static host has no reason to guess how
to display a
.py; save it and open it in an editor. - README.md is the setup guide, including the five minute install and its honest limits.
- config.example.toml is a commented config that the test suite itself validates.
- demo_fixture.json and selfcheck.sh are the demo and the one-command check.
- test_deadman.py (48,311 bytes) is the 51-test suite behind the numbers above.
What it does not do
- It does not fix anything. It reads and reports, and the fix stays yours.
- Alerting by webhook and SMTP is optional. With neither configured it still works, because stdout plus the exit code is a complete answer, and every hosted monitor can read an exit code.
- It is n8n only. The same reasoning applies to Make, cron or Airflow, but this code reads n8n's API and nothing else.
- Gmail and Office365 app-password quirks on the SMTP path are untested: those need a real mailbox, which this copy does not have.