A success alert cannot tell you what never started
I moved several scheduled GTM automations to GitHub Actions: account-scoring refreshes, engagement-intent reports, BDR performance tracking, outreach-gap detection, and intelligence extraction.
The setup had attractive properties. Workflow files lived beside the code. Secrets, logs, and manual dispatch were available. The compute was practical for daily jobs.
Then I compared thirty days of expected schedules with actual run history. Median start delays across workflows ranged from 47 minutes to three hours. The worst case was more than four hours late. Another daily schedule had run only twice in six weeks.
Every job posted to Slack, but Slack was a newspaper. I was reading the channel and hoping I noticed which expected story never appeared.
The operational question was simpler: did everything that should run today actually run?
Scheduled does not mean exact
GitHub's workflow troubleshooting documentation states that scheduled events can be delayed during high load, especially around the start of the hour, and that sufficiently loaded queues may drop jobs. GitHub also documents that scheduled workflows in public repositories can be automatically disabled after 60 days without repository activity.
Those behaviours are acceptable for many maintenance tasks. They become material when a revenue team assumes a score, report, or action queue is fresh.
Instead of demanding perfect scheduling from a best-effort system, define an acceptable window and monitor the business expectation independently.
Build an expected-run registry
Monitoring begins with what should happen.
For every job, store:
- stable job ID and display name;
- runtime and repository or project;
- schedule in UTC;
- business timezone;
- acceptable start and completion window;
- business owner and technical owner;
- criticality;
- idempotency and replay safety;
- manual-dispatch mechanism;
- upstream and downstream dependencies;
- alert and escalation rules.
This registry lets one monitor compare GitHub Actions, n8n, Trigger.dev, and custom services without pretending they expose identical APIs.
Use five states, not pass or fail
My day timeline uses operational states that answer different questions.
Upcoming
The expected window has not started. No action is required.
Running
The runtime reports an active execution. Track start time and expected duration.
Completed
The correct job completed inside the accepted window. Store the run link, commit, conclusion, and relevant output metadata.
Failed
A run started and returned a failure, cancellation, or invalid result. The runtime usually has logs for this state.
Missing or late
The expected start window passed and no matching run exists. This is the state that success alerts cannot detect.
Keep “late but running” separate from “never started” when the business consequence differs.
Match runs carefully
Do not identify a run only by workflow display name. Names change and repositories can contain similarly named jobs.
Use stable repository and workflow identifiers, the expected schedule window, event type, branch, and run creation time. Ignore manual or push-triggered runs unless they satisfy the same business job and output contract.
Store timestamps in UTC and convert for display. Daylight-saving changes and local-midnight boundaries can otherwise create false missing-run alerts.
Add safe recovery
My monitor includes one-click rerun or dispatch for eligible failures and missed starts. That convenience requires design work inside the job.
A replay-safe workflow should:
- use an explicit business date or time window;
- write idempotently or detect an existing completed output;
- separate extraction from irreversible external actions;
- prevent concurrent runs for the same logical window;
- record whether the run was scheduled, manual, or watchdog-triggered;
- expose enough metadata to verify the recovered output.
Do not automatically replay workflows that send customer messages, mutate financial records, or can duplicate irreversible work. Alert a human with the required context instead.
Give the debugger usable context
A “workflow failed” message transfers investigation work to the operator. A useful alert contains:
- expected job and window;
- observed state and delay;
- repository, workflow, and run link when available;
- failing step or concise error category;
- last successful run;
- recent repeated failures;
- owner and recovery option;
- safe copyable context for debugging.
My dashboard includes a copy-context action so I can bring failure logs and metadata into a coding session without manually reconstructing the incident.
Sanitise secrets and customer data before making logs copyable or posting them into Slack.
Use an independent watchdog
The system checking a scheduler should not depend solely on the same scheduler.
I use a separate n8n watchdog that checks expected jobs every fifteen minutes. When a safe GitHub workflow is missing, it can dispatch the job and post that it recovered a real missed run. The watchdog caught one on its first tick.
For critical workflows, separate the monitor's runtime, credentials, and alert path from the jobs it observes. The degree of independence should match the business impact.
Monitor data freshness too
A green runtime conclusion does not guarantee a useful business output.
Add checks such as:
- expected row or account coverage;
- source-data freshness;
- empty or unexpectedly small results;
- schema version;
- output timestamp;
- downstream write count;
- rejected-record count;
- model or API cost anomalies.
A workflow can complete successfully after reading stale input or writing zero records. Operational and data-quality checks belong together.
Measure the reliability system
Track:
- expected versus completed runs;
- late-start rate and delay distribution;
- missing-run rate;
- failed-run rate;
- mean time to detection;
- mean time to recovery;
- automatic replay success;
- repeated incident categories;
- workflows without current owners.
Use the trends to fix root causes. Shift schedules away from crowded boundaries, improve API retries, rotate credentials before expiry, or move exact-time critical jobs to a scheduler with stronger guarantees.
Start with a simple daily table
You do not need a custom application on day one. A registry, a script that queries run history, and a table of expected versus observed jobs can expose the problem.
Add the timeline, re-run controls, and cross-runtime adapters after the comparison proves useful. The most important architectural move is switching from event observation to expectation monitoring.
This pattern applies beyond GitHub Actions. Every production n8n GTM workflow and AI GTM system needs a way to answer what should have happened, what actually happened, and who owns the difference.